xref: /openbsd-src/usr.sbin/httpd/httpd.c (revision 50b7afb2c2c0993b0894d4e34bf857cb13ed9c80)
1 /*	$OpenBSD: httpd.c,v 1.2 2014/07/13 14:17:37 reyk Exp $	*/
2 
3 /*
4  * Copyright (c) 2014 Reyk Floeter <reyk@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 #include <sys/queue.h>
21 #include <sys/socket.h>
22 #include <sys/wait.h>
23 #include <sys/resource.h>
24 #include <sys/hash.h>
25 
26 #include <net/if.h>
27 #include <netinet/in.h>
28 #include <arpa/inet.h>
29 
30 #include <string.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <fcntl.h>
34 #include <getopt.h>
35 #include <fnmatch.h>
36 #include <err.h>
37 #include <errno.h>
38 #include <event.h>
39 #include <unistd.h>
40 #include <ctype.h>
41 #include <pwd.h>
42 #include <sha1.h>
43 #include <md5.h>
44 
45 #include <openssl/ssl.h>
46 
47 #include "httpd.h"
48 
49 __dead void	 usage(void);
50 
51 int		 parent_configure(struct httpd *);
52 void		 parent_configure_done(struct httpd *);
53 void		 parent_reload(struct httpd *, u_int, const char *);
54 void		 parent_sig_handler(int, short, void *);
55 void		 parent_shutdown(struct httpd *);
56 int		 parent_dispatch_server(int, struct privsep_proc *,
57 		    struct imsg *);
58 
59 struct httpd			*httpd_env;
60 
61 static struct privsep_proc procs[] = {
62 	{ "server",	PROC_SERVER, parent_dispatch_server, server }
63 };
64 
65 void
66 parent_sig_handler(int sig, short event, void *arg)
67 {
68 	struct privsep	*ps = arg;
69 	int		 die = 0, status, fail, id;
70 	pid_t		 pid;
71 	char		*cause;
72 
73 	switch (sig) {
74 	case SIGTERM:
75 	case SIGINT:
76 		die = 1;
77 		/* FALLTHROUGH */
78 	case SIGCHLD:
79 		do {
80 			pid = waitpid(WAIT_ANY, &status, WNOHANG);
81 			if (pid <= 0)
82 				continue;
83 
84 			fail = 0;
85 			if (WIFSIGNALED(status)) {
86 				fail = 1;
87 				asprintf(&cause, "terminated; signal %d",
88 				    WTERMSIG(status));
89 			} else if (WIFEXITED(status)) {
90 				if (WEXITSTATUS(status) != 0) {
91 					fail = 1;
92 					asprintf(&cause, "exited abnormally");
93 				} else
94 					asprintf(&cause, "exited okay");
95 			} else
96 				fatalx("unexpected cause of SIGCHLD");
97 
98 			die = 1;
99 
100 			for (id = 0; id < PROC_MAX; id++)
101 				if (pid == ps->ps_pid[id]) {
102 					if (fail)
103 						log_warnx("lost child: %s %s",
104 						    ps->ps_title[id], cause);
105 					break;
106 				}
107 
108 			free(cause);
109 		} while (pid > 0 || (pid == -1 && errno == EINTR));
110 
111 		if (die)
112 			parent_shutdown(ps->ps_env);
113 		break;
114 	case SIGHUP:
115 		log_info("%s: reload requested with SIGHUP", __func__);
116 
117 		/*
118 		 * This is safe because libevent uses async signal handlers
119 		 * that run in the event loop and not in signal context.
120 		 */
121 		parent_reload(ps->ps_env, CONFIG_RELOAD, NULL);
122 		break;
123 	case SIGPIPE:
124 		/* ignore */
125 		break;
126 	default:
127 		fatalx("unexpected signal");
128 	}
129 }
130 
131 __dead void
132 usage(void)
133 {
134 	extern char	*__progname;
135 
136 	fprintf(stderr, "usage: %s [-dnv] [-D macro=value] [-f file]\n",
137 	    __progname);
138 	exit(1);
139 }
140 
141 int
142 main(int argc, char *argv[])
143 {
144 	int			 c;
145 	int			 debug = 0, verbose = 0;
146 	u_int32_t		 opts = 0;
147 	struct httpd		*env;
148 	struct privsep		*ps;
149 	const char		*conffile = CONF_FILE;
150 
151 	while ((c = getopt(argc, argv, "dD:nf:v")) != -1) {
152 		switch (c) {
153 		case 'd':
154 			debug = 2;
155 			break;
156 		case 'D':
157 			if (cmdline_symset(optarg) < 0)
158 				log_warnx("could not parse macro definition %s",
159 				    optarg);
160 			break;
161 		case 'n':
162 			debug = 2;
163 			opts |= HTTPD_OPT_NOACTION;
164 			break;
165 		case 'f':
166 			conffile = optarg;
167 			break;
168 		case 'v':
169 			verbose++;
170 			opts |= HTTPD_OPT_VERBOSE;
171 			break;
172 		default:
173 			usage();
174 		}
175 	}
176 
177 	log_init(debug ? debug : 1);	/* log to stderr until daemonized */
178 
179 	argc -= optind;
180 	if (argc > 0)
181 		usage();
182 
183 	if ((env = calloc(1, sizeof(*env))) == NULL ||
184 	    (ps = calloc(1, sizeof(*ps))) == NULL)
185 		exit(1);
186 
187 	httpd_env = env;
188 	env->sc_ps = ps;
189 	ps->ps_env = env;
190 	TAILQ_INIT(&ps->ps_rcsocks);
191 	env->sc_conffile = conffile;
192 	env->sc_opts = opts;
193 
194 	if (parse_config(env->sc_conffile, env) == -1)
195 		exit(1);
196 
197 	if (debug)
198 		env->sc_opts |= HTTPD_OPT_LOGUPDATE;
199 
200 	if (geteuid())
201 		errx(1, "need root privileges");
202 
203 	if ((ps->ps_pw =  getpwnam(HTTPD_USER)) == NULL)
204 		errx(1, "unknown user %s", HTTPD_USER);
205 
206 	/* Configure the control socket */
207 	ps->ps_csock.cs_name = HTTPD_SOCKET;
208 
209 	log_init(debug);
210 	log_verbose(verbose);
211 
212 	if (!debug && daemon(1, 0) == -1)
213 		err(1, "failed to daemonize");
214 
215 	if (env->sc_opts & HTTPD_OPT_NOACTION)
216 		ps->ps_noaction = 1;
217 	else
218 		log_info("startup");
219 
220 	ps->ps_instances[PROC_SERVER] = env->sc_prefork_server;
221 	ps->ps_ninstances = env->sc_prefork_server;
222 
223 	proc_init(ps, procs, nitems(procs));
224 
225 	setproctitle("parent");
226 
227 	event_init();
228 
229 	signal_set(&ps->ps_evsigint, SIGINT, parent_sig_handler, ps);
230 	signal_set(&ps->ps_evsigterm, SIGTERM, parent_sig_handler, ps);
231 	signal_set(&ps->ps_evsigchld, SIGCHLD, parent_sig_handler, ps);
232 	signal_set(&ps->ps_evsighup, SIGHUP, parent_sig_handler, ps);
233 	signal_set(&ps->ps_evsigpipe, SIGPIPE, parent_sig_handler, ps);
234 
235 	signal_add(&ps->ps_evsigint, NULL);
236 	signal_add(&ps->ps_evsigterm, NULL);
237 	signal_add(&ps->ps_evsigchld, NULL);
238 	signal_add(&ps->ps_evsighup, NULL);
239 	signal_add(&ps->ps_evsigpipe, NULL);
240 
241 	proc_listen(ps, procs, nitems(procs));
242 
243 	if (load_config(env->sc_conffile, env) == -1) {
244 		proc_kill(env->sc_ps);
245 		exit(1);
246 	}
247 
248 	if (env->sc_opts & HTTPD_OPT_NOACTION) {
249 		fprintf(stderr, "configuration OK\n");
250 		proc_kill(env->sc_ps);
251 		exit(0);
252 	}
253 
254 	if (parent_configure(env) == -1)
255 		fatalx("configuration failed");
256 
257 	event_dispatch();
258 
259 	parent_shutdown(env);
260 	/* NOTREACHED */
261 
262 	return (0);
263 }
264 
265 int
266 parent_configure(struct httpd *env)
267 {
268 	int			 id;
269 	struct ctl_flags	 cf;
270 	int			 ret = -1;
271 	struct server		*srv;
272 	struct media_type	*media;
273 
274 	RB_FOREACH(media, mediatypes, env->sc_mediatypes) {
275 		if (config_setmedia(env, media) == -1)
276 			fatal("send media");
277 	}
278 
279 	TAILQ_FOREACH(srv, env->sc_servers, srv_entry) {
280 		if (config_setserver(env, srv) == -1)
281 			fatal("send server");
282 	}
283 
284 	/* The servers need to reload their config. */
285 	env->sc_reload = env->sc_prefork_server;
286 
287 	for (id = 0; id < PROC_MAX; id++) {
288 		if (id == privsep_process)
289 			continue;
290 		cf.cf_opts = env->sc_opts;
291 		cf.cf_flags = env->sc_flags;
292 
293 		proc_compose_imsg(env->sc_ps, id, -1, IMSG_CFG_DONE, -1,
294 		    &cf, sizeof(cf));
295 	}
296 
297 	ret = 0;
298 
299 	config_purge(env, CONFIG_ALL);
300 	return (ret);
301 }
302 
303 void
304 parent_reload(struct httpd *env, u_int reset, const char *filename)
305 {
306 	if (env->sc_reload) {
307 		log_debug("%s: already in progress: %d pending",
308 		    __func__, env->sc_reload);
309 		return;
310 	}
311 
312 	/* Switch back to the default config file */
313 	if (filename == NULL || *filename == '\0')
314 		filename = env->sc_conffile;
315 
316 	log_debug("%s: level %d config file %s", __func__, reset, filename);
317 
318 	config_purge(env, CONFIG_ALL);
319 
320 	if (reset == CONFIG_RELOAD) {
321 		if (load_config(filename, env) == -1) {
322 			log_debug("%s: failed to load config file %s",
323 			    __func__, filename);
324 		}
325 
326 		config_setreset(env, CONFIG_ALL);
327 
328 		if (parent_configure(env) == -1) {
329 			log_debug("%s: failed to commit config from %s",
330 			    __func__, filename);
331 		}
332 	} else
333 		config_setreset(env, reset);
334 }
335 
336 void
337 parent_configure_done(struct httpd *env)
338 {
339 	int	 id;
340 
341 	if (env->sc_reload == 0) {
342 		log_warnx("%s: configuration already finished", __func__);
343 		return;
344 	}
345 
346 	env->sc_reload--;
347 	if (env->sc_reload == 0) {
348 		for (id = 0; id < PROC_MAX; id++) {
349 			if (id == privsep_process)
350 				continue;
351 
352 			proc_compose_imsg(env->sc_ps, id, -1, IMSG_CTL_START,
353 			    -1, NULL, 0);
354 		}
355 	}
356 }
357 
358 void
359 parent_shutdown(struct httpd *env)
360 {
361 	config_purge(env, CONFIG_ALL);
362 
363 	proc_kill(env->sc_ps);
364 	control_cleanup(&env->sc_ps->ps_csock);
365 
366 	free(env->sc_ps);
367 	free(env);
368 
369 	log_info("parent terminating, pid %d", getpid());
370 
371 	exit(0);
372 }
373 
374 int
375 parent_dispatch_server(int fd, struct privsep_proc *p, struct imsg *imsg)
376 {
377 	struct httpd		*env = p->p_env;
378 
379 	switch (imsg->hdr.type) {
380 	case IMSG_CFG_DONE:
381 		parent_configure_done(env);
382 		break;
383 	default:
384 		return (-1);
385 	}
386 
387 	return (0);
388 }
389 
390 /*
391  * Utility functions
392  */
393 
394 void
395 event_again(struct event *ev, int fd, short event,
396     void (*fn)(int, short, void *),
397     struct timeval *start, struct timeval *end, void *arg)
398 {
399 	struct timeval tv_next, tv_now, tv;
400 
401 	getmonotime(&tv_now);
402 	memcpy(&tv_next, end, sizeof(tv_next));
403 	timersub(&tv_now, start, &tv_now);
404 	timersub(&tv_next, &tv_now, &tv_next);
405 
406 	memset(&tv, 0, sizeof(tv));
407 	if (timercmp(&tv_next, &tv, >))
408 		memcpy(&tv, &tv_next, sizeof(tv));
409 
410 	event_del(ev);
411 	event_set(ev, fd, event, fn, arg);
412 	event_add(ev, &tv);
413 }
414 
415 const char *
416 canonicalize_host(const char *host, char *name, size_t len)
417 {
418 	struct sockaddr_in	 sin4;
419 	struct sockaddr_in6	 sin6;
420 	u_int			 i, j;
421 	size_t			 plen;
422 	char			 c;
423 
424 	if (len < 2)
425 		goto fail;
426 
427 	/*
428 	 * Canonicalize an IPv4/6 address
429 	 */
430 	if (inet_pton(AF_INET, host, &sin4) == 1)
431 		return (inet_ntop(AF_INET, &sin4, name, len));
432 	if (inet_pton(AF_INET6, host, &sin6) == 1)
433 		return (inet_ntop(AF_INET6, &sin6, name, len));
434 
435 	/*
436 	 * Canonicalize a hostname
437 	 */
438 
439 	/* 1. remove repeated dots and convert upper case to lower case */
440 	plen = strlen(host);
441 	memset(name, 0, len);
442 	for (i = j = 0; i < plen; i++) {
443 		if (j >= (len - 1))
444 			goto fail;
445 		c = tolower(host[i]);
446 		if ((c == '.') && (j == 0 || name[j - 1] == '.'))
447 			continue;
448 		name[j++] = c;
449 	}
450 
451 	/* 2. remove trailing dots */
452 	for (i = j; i > 0; i--) {
453 		if (name[i - 1] != '.')
454 			break;
455 		name[i - 1] = '\0';
456 		j--;
457 	}
458 	if (j <= 0)
459 		goto fail;
460 
461 	return (name);
462 
463  fail:
464 	errno = EINVAL;
465 	return (NULL);
466 }
467 
468 void
469 socket_rlimit(int maxfd)
470 {
471 	struct rlimit	 rl;
472 
473 	if (getrlimit(RLIMIT_NOFILE, &rl) == -1)
474 		fatal("socket_rlimit: failed to get resource limit");
475 	log_debug("%s: max open files %llu", __func__, rl.rlim_max);
476 
477 	/*
478 	 * Allow the maximum number of open file descriptors for this
479 	 * login class (which should be the class "daemon" by default).
480 	 */
481 	if (maxfd == -1)
482 		rl.rlim_cur = rl.rlim_max;
483 	else
484 		rl.rlim_cur = MAX(rl.rlim_max, (rlim_t)maxfd);
485 	if (setrlimit(RLIMIT_NOFILE, &rl) == -1)
486 		fatal("socket_rlimit: failed to set resource limit");
487 }
488 
489 char *
490 get_string(u_int8_t *ptr, size_t len)
491 {
492 	size_t	 i;
493 	char	*str;
494 
495 	for (i = 0; i < len; i++)
496 		if (!(isprint(ptr[i]) || isspace(ptr[i])))
497 			break;
498 
499 	if ((str = calloc(1, i + 1)) == NULL)
500 		return (NULL);
501 	memcpy(str, ptr, i);
502 
503 	return (str);
504 }
505 
506 void *
507 get_data(u_int8_t *ptr, size_t len)
508 {
509 	u_int8_t	*data;
510 
511 	if ((data = calloc(1, len)) == NULL)
512 		return (NULL);
513 	memcpy(data, ptr, len);
514 
515 	return (data);
516 }
517 
518 int
519 accept_reserve(int sockfd, struct sockaddr *addr, socklen_t *addrlen,
520     int reserve, volatile int *counter)
521 {
522 	int ret;
523 	if (getdtablecount() + reserve +
524 	    *counter >= getdtablesize()) {
525 		errno = EMFILE;
526 		return (-1);
527 	}
528 
529 	if ((ret = accept(sockfd, addr, addrlen)) > -1) {
530 		(*counter)++;
531 		DPRINTF("%s: inflight incremented, now %d",__func__, *counter);
532 	}
533 	return (ret);
534 }
535 
536 struct kv *
537 kv_add(struct kvtree *keys, char *key, char *value)
538 {
539 	struct kv	*kv, *oldkv;
540 
541 	if (key == NULL)
542 		return (NULL);
543 	if ((kv = calloc(1, sizeof(*kv))) == NULL)
544 		return (NULL);
545 	if ((kv->kv_key = strdup(key)) == NULL) {
546 		free(kv);
547 		return (NULL);
548 	}
549 	if (value != NULL &&
550 	    (kv->kv_value = strdup(value)) == NULL) {
551 		free(kv->kv_key);
552 		free(kv);
553 		return (NULL);
554 	}
555 	TAILQ_INIT(&kv->kv_children);
556 
557 	if ((oldkv = RB_INSERT(kvtree, keys, kv)) != NULL) {
558 		TAILQ_INSERT_TAIL(&oldkv->kv_children, kv, kv_entry);
559 		kv->kv_parent = oldkv;
560 	}
561 
562 	return (kv);
563 }
564 
565 int
566 kv_set(struct kv *kv, char *fmt, ...)
567 {
568 	va_list		  ap;
569 	char		*value = NULL;
570 	struct kv	*ckv;
571 
572 	va_start(ap, fmt);
573 	if (vasprintf(&value, fmt, ap) == -1)
574 		return (-1);
575 	va_end(ap);
576 
577 	/* Remove all children */
578 	while ((ckv = TAILQ_FIRST(&kv->kv_children)) != NULL) {
579 		TAILQ_REMOVE(&kv->kv_children, ckv, kv_entry);
580 		kv_free(ckv);
581 		free(ckv);
582 	}
583 
584 	/* Set the new value */
585 	if (kv->kv_value != NULL)
586 		free(kv->kv_value);
587 	kv->kv_value = value;
588 
589 	return (0);
590 }
591 
592 int
593 kv_setkey(struct kv *kv, char *fmt, ...)
594 {
595 	va_list  ap;
596 	char	*key = NULL;
597 
598 	va_start(ap, fmt);
599 	if (vasprintf(&key, fmt, ap) == -1)
600 		return (-1);
601 	va_end(ap);
602 
603 	if (kv->kv_key != NULL)
604 		free(kv->kv_key);
605 	kv->kv_key = key;
606 
607 	return (0);
608 }
609 
610 void
611 kv_delete(struct kvtree *keys, struct kv *kv)
612 {
613 	struct kv	*ckv;
614 
615 	RB_REMOVE(kvtree, keys, kv);
616 
617 	/* Remove all children */
618 	while ((ckv = TAILQ_FIRST(&kv->kv_children)) != NULL) {
619 		TAILQ_REMOVE(&kv->kv_children, ckv, kv_entry);
620 		kv_free(ckv);
621 		free(ckv);
622 	}
623 
624 	kv_free(kv);
625 	free(kv);
626 }
627 
628 struct kv *
629 kv_extend(struct kvtree *keys, struct kv *kv, char *value)
630 {
631 	char		*newvalue;
632 
633 	if (kv == NULL) {
634 		return (NULL);
635 	} else if (kv->kv_value != NULL) {
636 		if (asprintf(&newvalue, "%s%s", kv->kv_value, value) == -1)
637 			return (NULL);
638 
639 		free(kv->kv_value);
640 		kv->kv_value = newvalue;
641 	} else if ((kv->kv_value = strdup(value)) == NULL)
642 		return (NULL);
643 
644 	return (kv);
645 }
646 
647 void
648 kv_purge(struct kvtree *keys)
649 {
650 	struct kv	*kv;
651 
652 	while ((kv = RB_MIN(kvtree, keys)) != NULL)
653 		kv_delete(keys, kv);
654 }
655 
656 void
657 kv_free(struct kv *kv)
658 {
659 	if (kv->kv_type == KEY_TYPE_NONE)
660 		return;
661 	if (kv->kv_key != NULL) {
662 		free(kv->kv_key);
663 	}
664 	kv->kv_key = NULL;
665 	if (kv->kv_value != NULL) {
666 		free(kv->kv_value);
667 	}
668 	kv->kv_value = NULL;
669 	memset(kv, 0, sizeof(*kv));
670 }
671 
672 struct kv *
673 kv_inherit(struct kv *dst, struct kv *src)
674 {
675 	memset(dst, 0, sizeof(*dst));
676 	memcpy(dst, src, sizeof(*dst));
677 	TAILQ_INIT(&dst->kv_children);
678 
679 	if (src->kv_key != NULL) {
680 		if ((dst->kv_key = strdup(src->kv_key)) == NULL) {
681 			kv_free(dst);
682 			return (NULL);
683 		}
684 	}
685 	if (src->kv_value != NULL) {
686 		if ((dst->kv_value = strdup(src->kv_value)) == NULL) {
687 			kv_free(dst);
688 			return (NULL);
689 		}
690 	}
691 
692 	return (dst);
693 }
694 
695 int
696 kv_log(struct evbuffer *log, struct kv *kv)
697 {
698 	char	*msg;
699 
700 	if (log == NULL)
701 		return (0);
702 	if (asprintf(&msg, " [%s%s%s]",
703 	    kv->kv_key == NULL ? "(unknown)" : kv->kv_key,
704 	    kv->kv_value == NULL ? "" : ": ",
705 	    kv->kv_value == NULL ? "" : kv->kv_value) == -1)
706 		return (-1);
707 	if (evbuffer_add(log, msg, strlen(msg)) == -1) {
708 		free(msg);
709 		return (-1);
710 	}
711 	free(msg);
712 
713 	return (0);
714 }
715 
716 struct kv *
717 kv_find(struct kvtree *keys, struct kv *kv)
718 {
719 	struct kv	*match;
720 	const char	*key;
721 
722 	if (kv->kv_flags & KV_FLAG_GLOBBING) {
723 		/* Test header key using shell globbing rules */
724 		key = kv->kv_key == NULL ? "" : kv->kv_key;
725 		RB_FOREACH(match, kvtree, keys) {
726 			if (fnmatch(key, match->kv_key, FNM_CASEFOLD) == 0)
727 				break;
728 		}
729 	} else {
730 		/* Fast tree-based lookup only works without globbing */
731 		match = RB_FIND(kvtree, keys, kv);
732 	}
733 
734 	return (match);
735 }
736 
737 int
738 kv_cmp(struct kv *a, struct kv *b)
739 {
740 	return (strcasecmp(a->kv_key, b->kv_key));
741 }
742 
743 RB_GENERATE(kvtree, kv, kv_node, kv_cmp);
744 
745 struct media_type *
746 media_add(struct mediatypes *types, struct media_type *media)
747 {
748 	struct media_type	*entry;
749 
750 	if ((entry = RB_FIND(mediatypes, types, media)) != NULL) {
751 		log_debug("%s: duplicated entry for \"%s\"", __func__,
752 		    media->media_name);
753 		return (NULL);
754 	}
755 
756 	if ((entry = malloc(sizeof(*media))) == NULL)
757 		return (NULL);
758 
759 	memcpy(entry, media, sizeof(*entry));
760 	RB_INSERT(mediatypes, types, entry);
761 
762 	return (entry);
763 }
764 
765 void
766 media_delete(struct mediatypes *types, struct media_type *media)
767 {
768 	RB_REMOVE(mediatypes, types, media);
769 	if (media->media_encoding != NULL)
770 		free(media->media_encoding);
771 	free(media);
772 }
773 
774 void
775 media_purge(struct mediatypes *types)
776 {
777 	struct media_type	*media;
778 
779 	while ((media = RB_MIN(mediatypes, types)) != NULL)
780 		media_delete(types, media);
781 }
782 
783 struct media_type *
784 media_find(struct mediatypes *types, char *file)
785 {
786 	struct media_type	*match, media;
787 	char			*p;
788 
789 	if ((p = strrchr(file, '.')) == NULL) {
790 		p = file;
791 	} else if (*p++ == '\0') {
792 		return (NULL);
793 	}
794 	if (strlcpy(media.media_name, p,
795 	    sizeof(media.media_name)) >=
796 	    sizeof(media.media_name)) {
797 		return (NULL);
798 	}
799 
800 	/* Find media type by extension name */
801 	match = RB_FIND(mediatypes, types, &media);
802 
803 	return (match);
804 }
805 
806 int
807 media_cmp(struct media_type *a, struct media_type *b)
808 {
809 	return (strcasecmp(a->media_name, b->media_name));
810 }
811 
812 RB_GENERATE(mediatypes, media_type, media_entry, media_cmp);
813