xref: /openbsd/usr.sbin/smtpd/util.c (revision 8e996a8e)
1 /*	$OpenBSD: util.c,v 1.158 2024/05/13 06:48:26 jsg Exp $	*/
2 
3 /*
4  * Copyright (c) 2000,2001 Markus Friedl.  All rights reserved.
5  * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org>
6  * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net>
7  * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
8  *
9  * Permission to use, copy, modify, and distribute this software for any
10  * purpose with or without fee is hereby granted, provided that the above
11  * copyright notice and this permission notice appear in all copies.
12  *
13  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20  */
21 
22 #include <sys/stat.h>
23 
24 #include <netinet/in.h>
25 
26 #include <arpa/inet.h>
27 #include <ctype.h>
28 #include <errno.h>
29 #include <fts.h>
30 #include <libgen.h>
31 #include <resolv.h>
32 #include <stdarg.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <syslog.h>
36 #include <unistd.h>
37 
38 #include "smtpd.h"
39 #include "log.h"
40 
41 const char *log_in6addr(const struct in6_addr *);
42 const char *log_sockaddr(struct sockaddr *);
43 static int  parse_mailname_file(char *, size_t);
44 
45 int	tracing = 0;
46 int	foreground_log = 0;
47 
48 void *
xmalloc(size_t size)49 xmalloc(size_t size)
50 {
51 	void	*r;
52 
53 	if ((r = malloc(size)) == NULL)
54 		fatal("malloc");
55 
56 	return (r);
57 }
58 
59 void *
xcalloc(size_t nmemb,size_t size)60 xcalloc(size_t nmemb, size_t size)
61 {
62 	void	*r;
63 
64 	if ((r = calloc(nmemb, size)) == NULL)
65 		fatal("calloc");
66 
67 	return (r);
68 }
69 
70 char *
xstrdup(const char * str)71 xstrdup(const char *str)
72 {
73 	char	*r;
74 
75 	if ((r = strdup(str)) == NULL)
76 		fatal("strdup");
77 
78 	return (r);
79 }
80 
81 void *
xmemdup(const void * ptr,size_t size)82 xmemdup(const void *ptr, size_t size)
83 {
84 	void	*r;
85 
86 	if ((r = malloc(size)) == NULL)
87 		fatal("malloc");
88 
89 	memmove(r, ptr, size);
90 
91 	return (r);
92 }
93 
94 int
xasprintf(char ** ret,const char * format,...)95 xasprintf(char **ret, const char *format, ...)
96 {
97 	int r;
98 	va_list ap;
99 
100 	va_start(ap, format);
101 	r = vasprintf(ret, format, ap);
102 	va_end(ap);
103 	if (r == -1)
104 		fatal("vasprintf");
105 
106 	return (r);
107 }
108 
109 
110 #if !defined(NO_IO)
111 int
io_xprintf(struct io * io,const char * fmt,...)112 io_xprintf(struct io *io, const char *fmt, ...)
113 {
114 	va_list	ap;
115 	int len;
116 
117 	va_start(ap, fmt);
118 	len = io_vprintf(io, fmt, ap);
119 	va_end(ap);
120 	if (len == -1)
121 		fatal("io_xprintf(%p, %s, ...)", io, fmt);
122 
123 	return len;
124 }
125 
126 int
io_xprint(struct io * io,const char * str)127 io_xprint(struct io *io, const char *str)
128 {
129 	int len;
130 
131 	len = io_print(io, str);
132 	if (len == -1)
133 		fatal("io_xprint(%p, %s, ...)", io, str);
134 
135 	return len;
136 }
137 #endif
138 
139 char *
strip(char * s)140 strip(char *s)
141 {
142 	size_t	 l;
143 
144 	while (isspace((unsigned char)*s))
145 		s++;
146 
147 	for (l = strlen(s); l; l--) {
148 		if (!isspace((unsigned char)s[l-1]))
149 			break;
150 		s[l-1] = '\0';
151 	}
152 
153 	return (s);
154 }
155 
156 int
bsnprintf(char * str,size_t size,const char * format,...)157 bsnprintf(char *str, size_t size, const char *format, ...)
158 {
159 	int ret;
160 	va_list ap;
161 
162 	va_start(ap, format);
163 	ret = vsnprintf(str, size, format, ap);
164 	va_end(ap);
165 	if (ret < 0 || (size_t)ret >= size)
166 		return 0;
167 
168 	return 1;
169 }
170 
171 
172 int
ckdir(const char * path,mode_t mode,uid_t owner,gid_t group,int create)173 ckdir(const char *path, mode_t mode, uid_t owner, gid_t group, int create)
174 {
175 	char		mode_str[12];
176 	int		ret;
177 	struct stat	sb;
178 
179 	if (stat(path, &sb) == -1) {
180 		if (errno != ENOENT || create == 0) {
181 			log_warn("stat: %s", path);
182 			return (0);
183 		}
184 
185 		/* chmod is deferred to avoid umask effect */
186 		if (mkdir(path, 0) == -1) {
187 			log_warn("mkdir: %s", path);
188 			return (0);
189 		}
190 
191 		if (chown(path, owner, group) == -1) {
192 			log_warn("chown: %s", path);
193 			return (0);
194 		}
195 
196 		if (chmod(path, mode) == -1) {
197 			log_warn("chmod: %s", path);
198 			return (0);
199 		}
200 
201 		if (stat(path, &sb) == -1) {
202 			log_warn("stat: %s", path);
203 			return (0);
204 		}
205 	}
206 
207 	ret = 1;
208 
209 	/* check if it's a directory */
210 	if (!S_ISDIR(sb.st_mode)) {
211 		ret = 0;
212 		log_warnx("%s is not a directory", path);
213 	}
214 
215 	/* check that it is owned by owner/group */
216 	if (sb.st_uid != owner) {
217 		ret = 0;
218 		log_warnx("%s is not owned by uid %d", path, owner);
219 	}
220 	if (sb.st_gid != group) {
221 		ret = 0;
222 		log_warnx("%s is not owned by gid %d", path, group);
223 	}
224 
225 	/* check permission */
226 	if ((sb.st_mode & 07777) != mode) {
227 		ret = 0;
228 		strmode(mode, mode_str);
229 		mode_str[10] = '\0';
230 		log_warnx("%s must be %s (%o)", path, mode_str + 1, mode);
231 	}
232 
233 	return ret;
234 }
235 
236 int
rmtree(char * path,int keepdir)237 rmtree(char *path, int keepdir)
238 {
239 	char		*path_argv[2];
240 	FTS		*fts;
241 	FTSENT		*e;
242 	int		 ret, depth;
243 
244 	path_argv[0] = path;
245 	path_argv[1] = NULL;
246 	ret = 0;
247 	depth = 0;
248 
249 	fts = fts_open(path_argv, FTS_PHYSICAL | FTS_NOCHDIR, NULL);
250 	if (fts == NULL) {
251 		log_warn("fts_open: %s", path);
252 		return (-1);
253 	}
254 
255 	while ((e = fts_read(fts)) != NULL) {
256 		switch (e->fts_info) {
257 		case FTS_D:
258 			depth++;
259 			break;
260 		case FTS_DP:
261 		case FTS_DNR:
262 			depth--;
263 			if (keepdir && depth == 0)
264 				continue;
265 			if (rmdir(e->fts_path) == -1) {
266 				log_warn("rmdir: %s", e->fts_path);
267 				ret = -1;
268 			}
269 			break;
270 
271 		case FTS_F:
272 			if (unlink(e->fts_path) == -1) {
273 				log_warn("unlink: %s", e->fts_path);
274 				ret = -1;
275 			}
276 		}
277 	}
278 
279 	fts_close(fts);
280 
281 	return (ret);
282 }
283 
284 int
mvpurge(char * from,char * to)285 mvpurge(char *from, char *to)
286 {
287 	size_t		 n;
288 	int		 retry;
289 	const char	*sep;
290 	char		 buf[PATH_MAX];
291 
292 	if ((n = strlen(to)) == 0)
293 		fatalx("to is empty");
294 
295 	sep = (to[n - 1] == '/') ? "" : "/";
296 	retry = 0;
297 
298 again:
299 	(void)snprintf(buf, sizeof buf, "%s%s%u", to, sep, arc4random());
300 	if (rename(from, buf) == -1) {
301 		/* ENOTDIR has actually 2 meanings, and incorrect input
302 		 * could lead to an infinite loop. Consider that after
303 		 * 20 tries something is hopelessly wrong.
304 		 */
305 		if (errno == ENOTEMPTY || errno == EISDIR || errno == ENOTDIR) {
306 			if ((retry++) >= 20)
307 				return (-1);
308 			goto again;
309 		}
310 		return -1;
311 	}
312 
313 	return 0;
314 }
315 
316 
317 int
mktmpfile(void)318 mktmpfile(void)
319 {
320 	char		path[PATH_MAX];
321 	int		fd;
322 
323 	if (!bsnprintf(path, sizeof(path), "%s/smtpd.XXXXXXXXXX",
324 		PATH_TEMPORARY)) {
325 		log_warn("snprintf");
326 		fatal("exiting");
327 	}
328 
329 	if ((fd = mkstemp(path)) == -1) {
330 		log_warn("cannot create temporary file %s", path);
331 		fatal("exiting");
332 	}
333 	unlink(path);
334 	return (fd);
335 }
336 
337 
338 /* Close file, signifying temporary error condition (if any) to the caller. */
339 int
safe_fclose(FILE * fp)340 safe_fclose(FILE *fp)
341 {
342 	if (ferror(fp)) {
343 		fclose(fp);
344 		return 0;
345 	}
346 	if (fflush(fp)) {
347 		fclose(fp);
348 		if (errno == ENOSPC)
349 			return 0;
350 		fatal("safe_fclose: fflush");
351 	}
352 	if (fsync(fileno(fp)))
353 		fatal("safe_fclose: fsync");
354 	if (fclose(fp))
355 		fatal("safe_fclose: fclose");
356 
357 	return 1;
358 }
359 
360 int
hostname_match(const char * hostname,const char * pattern)361 hostname_match(const char *hostname, const char *pattern)
362 {
363 	while (*pattern != '\0' && *hostname != '\0') {
364 		if (*pattern == '*') {
365 			while (*pattern == '*')
366 				pattern++;
367 			while (*hostname != '\0' &&
368 			    tolower((unsigned char)*hostname) !=
369 			    tolower((unsigned char)*pattern))
370 				hostname++;
371 			continue;
372 		}
373 
374 		if (tolower((unsigned char)*pattern) !=
375 		    tolower((unsigned char)*hostname))
376 			return 0;
377 		pattern++;
378 		hostname++;
379 	}
380 
381 	return (*hostname == '\0' && *pattern == '\0');
382 }
383 
384 int
mailaddr_match(const struct mailaddr * maddr1,const struct mailaddr * maddr2)385 mailaddr_match(const struct mailaddr *maddr1, const struct mailaddr *maddr2)
386 {
387 	struct mailaddr m1 = *maddr1;
388 	struct mailaddr m2 = *maddr2;
389 	char	       *p;
390 
391 	/* catchall */
392 	if (m2.user[0] == '\0' && m2.domain[0] == '\0')
393 		return 1;
394 
395 	if (m2.domain[0] && !hostname_match(m1.domain, m2.domain))
396 		return 0;
397 
398 	if (m2.user[0]) {
399 		/* if address from table has a tag, we must respect it */
400 		if (strchr(m2.user, *env->sc_subaddressing_delim) == NULL) {
401 			/* otherwise, strip tag from session address if any */
402 			p = strchr(m1.user, *env->sc_subaddressing_delim);
403 			if (p)
404 				*p = '\0';
405 		}
406 		if (strcasecmp(m1.user, m2.user))
407 			return 0;
408 	}
409 	return 1;
410 }
411 
412 int
valid_localpart(const char * s)413 valid_localpart(const char *s)
414 {
415 #define IS_ATEXT(c) (isalnum((unsigned char)(c)) || strchr(MAILADDR_ALLOWED, (c)))
416 nextatom:
417 	if (!IS_ATEXT(*s) || *s == '\0')
418 		return 0;
419 	while (*(++s) != '\0') {
420 		if (*s == '.')
421 			break;
422 		if (IS_ATEXT(*s))
423 			continue;
424 		return 0;
425 	}
426 	if (*s == '.') {
427 		s++;
428 		goto nextatom;
429 	}
430 	return 1;
431 }
432 
433 int
valid_domainpart(const char * s)434 valid_domainpart(const char *s)
435 {
436 	struct in_addr	 ina;
437 	struct in6_addr	 ina6;
438 	char		*c, domain[SMTPD_MAXDOMAINPARTSIZE];
439 	const char	*p;
440 	size_t		 dlen;
441 
442 	if (*s == '[') {
443 		if (strncasecmp("[IPv6:", s, 6) == 0)
444 			p = s + 6;
445 		else
446 			p = s + 1;
447 
448 		if (strlcpy(domain, p, sizeof domain) >= sizeof domain)
449 			return 0;
450 
451 		c = strchr(domain, ']');
452 		if (!c || c[1] != '\0')
453 			return 0;
454 
455 		*c = '\0';
456 
457 		if (inet_pton(AF_INET6, domain, &ina6) == 1)
458 			return 1;
459 		if (inet_pton(AF_INET, domain, &ina) == 1)
460 			return 1;
461 
462 		return 0;
463 	}
464 
465 	if (*s == '\0')
466 		return 0;
467 
468 	dlen = strlen(s);
469 	if (dlen >= sizeof domain)
470 		return 0;
471 
472 	if (s[dlen - 1] == '.')
473 		return 0;
474 
475 	return res_hnok(s);
476 }
477 
478 #define LABELCHR(c) ((c) == '-' || (c) == '_' || isalpha((unsigned char)(c)) || isdigit((unsigned char)(c)))
479 #define LABELMAX 63
480 #define DNAMEMAX 253
481 
482 int
valid_domainname(const char * str)483 valid_domainname(const char *str)
484 {
485 	const char *label, *s;
486 
487 	/*
488 	 * Expect a sequence of dot-separated labels, possibly with a trailing
489 	 * dot. The empty string is rejected, as well a single dot.
490 	 */
491 	for (s = str; *s; s++) {
492 
493 		/* Start of a new label. */
494 		label = s;
495 		while (LABELCHR(*s))
496 			s++;
497 
498 		/* Must have at least one char and at most LABELMAX. */
499 		if (s == label || s - label > LABELMAX)
500 			return 0;
501 
502 		/* If last label, stop here. */
503 		if (*s == '\0')
504 			break;
505 
506 		/* Expect a dot as label separator or last char. */
507 		if (*s != '.')
508 			return 0;
509 	}
510 
511 	/* Must have at leat one label and no more than DNAMEMAX chars. */
512 	if (s == str || s - str > DNAMEMAX)
513 		return 0;
514 
515 	return 1;
516 }
517 
518 int
valid_smtp_response(const char * s)519 valid_smtp_response(const char *s)
520 {
521 	if (strlen(s) < 5)
522 		return 0;
523 
524 	if ((s[0] < '2' || s[0] > '5') ||
525 	    (s[1] < '0' || s[1] > '9') ||
526 	    (s[2] < '0' || s[2] > '9') ||
527 	    (s[3] != ' '))
528 		return 0;
529 
530 	return 1;
531 }
532 
533 int
valid_xtext(const char * s)534 valid_xtext(const char *s)
535 {
536 	for (; *s != '\0'; ++s) {
537 		if (*s < '!' || *s > '~' || *s == '=')
538 			return 0;
539 
540 		if (*s != '+')
541 			continue;
542 
543 		s++;
544 		if (!isdigit((unsigned char)*s) &&
545 		    !(*s >= 'A' && *s <= 'F'))
546 			return 0;
547 
548 		s++;
549 		if (!isdigit((unsigned char)*s) &&
550 		    !(*s >= 'A' && *s <= 'F'))
551 			return 0;
552 	}
553 
554 	return 1;
555 }
556 
557 int
secure_file(int fd,char * path,char * userdir,uid_t uid,int mayread)558 secure_file(int fd, char *path, char *userdir, uid_t uid, int mayread)
559 {
560 	char		 buf[PATH_MAX];
561 	char		 homedir[PATH_MAX];
562 	struct stat	 st;
563 	char		*cp;
564 
565 	if (realpath(path, buf) == NULL)
566 		return 0;
567 
568 	if (realpath(userdir, homedir) == NULL)
569 		homedir[0] = '\0';
570 
571 	/* Check the open file to avoid races. */
572 	if (fstat(fd, &st) == -1 ||
573 	    !S_ISREG(st.st_mode) ||
574 	    st.st_uid != uid ||
575 	    (st.st_mode & (mayread ? 022 : 066)) != 0)
576 		return 0;
577 
578 	/* For each component of the canonical path, walking upwards. */
579 	for (;;) {
580 		if ((cp = dirname(buf)) == NULL)
581 			return 0;
582 		(void)strlcpy(buf, cp, sizeof(buf));
583 
584 		if (stat(buf, &st) == -1 ||
585 		    (st.st_uid != 0 && st.st_uid != uid) ||
586 		    (st.st_mode & 022) != 0)
587 			return 0;
588 
589 		/* We can stop checking after reaching homedir level. */
590 		if (strcmp(homedir, buf) == 0)
591 			break;
592 
593 		/*
594 		 * dirname should always complete with a "/" path,
595 		 * but we can be paranoid and check for "." too
596 		 */
597 		if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0))
598 			break;
599 	}
600 
601 	return 1;
602 }
603 
604 void
addargs(arglist * args,char * fmt,...)605 addargs(arglist *args, char *fmt, ...)
606 {
607 	va_list ap;
608 	char *cp;
609 	uint nalloc;
610 	int r;
611 	char	**tmp;
612 
613 	va_start(ap, fmt);
614 	r = vasprintf(&cp, fmt, ap);
615 	va_end(ap);
616 	if (r == -1)
617 		fatal("addargs: argument too long");
618 
619 	nalloc = args->nalloc;
620 	if (args->list == NULL) {
621 		nalloc = 32;
622 		args->num = 0;
623 	} else if (args->num+2 >= nalloc)
624 		nalloc *= 2;
625 
626 	tmp = reallocarray(args->list, nalloc, sizeof(char *));
627 	if (tmp == NULL)
628 		fatal("addargs: reallocarray");
629 	args->list = tmp;
630 	args->nalloc = nalloc;
631 	args->list[args->num++] = cp;
632 	args->list[args->num] = NULL;
633 }
634 
635 int
lowercase(char * buf,const char * s,size_t len)636 lowercase(char *buf, const char *s, size_t len)
637 {
638 	if (len == 0)
639 		return 0;
640 
641 	if (strlcpy(buf, s, len) >= len)
642 		return 0;
643 
644 	while (*buf != '\0') {
645 		*buf = tolower((unsigned char)*buf);
646 		buf++;
647 	}
648 
649 	return 1;
650 }
651 
652 int
uppercase(char * buf,const char * s,size_t len)653 uppercase(char *buf, const char *s, size_t len)
654 {
655 	if (len == 0)
656 		return 0;
657 
658 	if (strlcpy(buf, s, len) >= len)
659 		return 0;
660 
661 	while (*buf != '\0') {
662 		*buf = toupper((unsigned char)*buf);
663 		buf++;
664 	}
665 
666 	return 1;
667 }
668 
669 void
xlowercase(char * buf,const char * s,size_t len)670 xlowercase(char *buf, const char *s, size_t len)
671 {
672 	if (len == 0)
673 		fatalx("lowercase: len == 0");
674 
675 	if (!lowercase(buf, s, len))
676 		fatalx("lowercase: truncation");
677 }
678 
679 uint64_t
generate_uid(void)680 generate_uid(void)
681 {
682 	static uint32_t id;
683 	static uint8_t	inited;
684 	uint64_t	uid;
685 
686 	if (!inited) {
687 		id = arc4random();
688 		inited = 1;
689 	}
690 	while ((uid = ((uint64_t)(id++) << 32 | arc4random())) == 0)
691 		;
692 
693 	return (uid);
694 }
695 
696 int
session_socket_error(int fd)697 session_socket_error(int fd)
698 {
699 	int		error;
700 	socklen_t	len;
701 
702 	len = sizeof(error);
703 	if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) == -1)
704 		fatal("session_socket_error: getsockopt");
705 
706 	return (error);
707 }
708 
709 const char *
parse_smtp_response(char * line,size_t len,char ** msg,int * cont)710 parse_smtp_response(char *line, size_t len, char **msg, int *cont)
711 {
712 	if (len >= LINE_MAX)
713 		return "line too long";
714 
715 	if (len > 3) {
716 		if (msg)
717 			*msg = line + 4;
718 		if (cont)
719 			*cont = (line[3] == '-');
720 	} else if (len == 3) {
721 		if (msg)
722 			*msg = line + 3;
723 		if (cont)
724 			*cont = 0;
725 	} else
726 		return "line too short";
727 
728 	/* validate reply code */
729 	if (line[0] < '2' || line[0] > '5' || !isdigit((unsigned char)line[1]) ||
730 	    !isdigit((unsigned char)line[2]))
731 		return "reply code out of range";
732 
733 	return NULL;
734 }
735 
736 static int
parse_mailname_file(char * hostname,size_t len)737 parse_mailname_file(char *hostname, size_t len)
738 {
739 	FILE	*fp;
740 	char	*buf = NULL;
741 	size_t	 bufsz = 0;
742 	ssize_t	 buflen;
743 
744 	if ((fp = fopen(MAILNAME_FILE, "r")) == NULL)
745 		return 1;
746 
747 	buflen = getline(&buf, &bufsz, fp);
748 	fclose(fp);
749 	if (buflen == -1) {
750 		free(buf);
751 		return 1;
752 	}
753 
754 	if (buf[buflen - 1] == '\n')
755 		buf[buflen - 1] = '\0';
756 
757 	bufsz = strlcpy(hostname, buf, len);
758 	free(buf);
759 	if (bufsz >= len) {
760 		fprintf(stderr, MAILNAME_FILE " entry too long");
761 		return 1;
762 	}
763 
764 	return 0;
765 }
766 
767 int
getmailname(char * hostname,size_t len)768 getmailname(char *hostname, size_t len)
769 {
770 	struct addrinfo	 hints, *res = NULL;
771 	int		 error;
772 
773 	/* Try MAILNAME_FILE first */
774 	if (parse_mailname_file(hostname, len) == 0)
775 		return 0;
776 
777 	/* Next, gethostname(3) */
778 	if (gethostname(hostname, len) == -1) {
779 		fprintf(stderr, "getmailname: gethostname() failed\n");
780 		return -1;
781 	}
782 
783 	if (strchr(hostname, '.') != NULL)
784 		return 0;
785 
786 	/* Canonicalize if domain part is missing */
787 	memset(&hints, 0, sizeof hints);
788 	hints.ai_family = PF_UNSPEC;
789 	hints.ai_flags = AI_CANONNAME;
790 	error = getaddrinfo(hostname, NULL, &hints, &res);
791 	if (error)
792 		return 0; /* Continue with non-canon hostname */
793 
794 	if (strlcpy(hostname, res->ai_canonname, len) >= len) {
795 		fprintf(stderr, "hostname too long");
796 		freeaddrinfo(res);
797 		return -1;
798 	}
799 
800 	freeaddrinfo(res);
801 	return 0;
802 }
803 
804 int
base64_encode(unsigned char const * src,size_t srclen,char * dest,size_t destsize)805 base64_encode(unsigned char const *src, size_t srclen,
806 	      char *dest, size_t destsize)
807 {
808 	return __b64_ntop(src, srclen, dest, destsize);
809 }
810 
811 int
base64_decode(char const * src,unsigned char * dest,size_t destsize)812 base64_decode(char const *src, unsigned char *dest, size_t destsize)
813 {
814 	return __b64_pton(src, dest, destsize);
815 }
816 
817 int
base64_encode_rfc3548(unsigned char const * src,size_t srclen,char * dest,size_t destsize)818 base64_encode_rfc3548(unsigned char const *src, size_t srclen,
819 	      char *dest, size_t destsize)
820 {
821 	size_t i;
822 	int ret;
823 
824 	if ((ret = base64_encode(src, srclen, dest, destsize)) == -1)
825 		return -1;
826 
827 	for (i = 0; i < destsize; ++i) {
828 		if (dest[i] == '/')
829 			dest[i] = '_';
830 		else if (dest[i] == '+')
831 			dest[i] = '-';
832 	}
833 
834 	return ret;
835 }
836 
837 void
log_trace0(const char * emsg,...)838 log_trace0(const char *emsg, ...)
839 {
840 	va_list	 ap;
841 
842 	va_start(ap, emsg);
843 	vlog(LOG_DEBUG, emsg, ap);
844 	va_end(ap);
845 }
846 
847 void
log_trace_verbose(int v)848 log_trace_verbose(int v)
849 {
850 	tracing = v;
851 
852 	/* Set debug logging in log.c */
853 	log_setverbose(v & TRACE_DEBUG ? 2 : foreground_log);
854 }
855 
856 int
parse_table_line(FILE * fp,char ** line,size_t * linesize,int * type,char ** key,char ** val,int * malformed)857 parse_table_line(FILE *fp, char **line, size_t *linesize,
858     int *type, char **key, char **val, int *malformed)
859 {
860 	char	*keyp, *valp;
861 	ssize_t	 linelen;
862 
863 	*key = NULL;
864 	*val = NULL;
865 	*malformed = 0;
866 
867 	if ((linelen = getline(line, linesize, fp)) == -1)
868 		return (-1);
869 
870 	keyp = *line;
871 	while (isspace((unsigned char)*keyp)) {
872 		++keyp;
873 		--linelen;
874 	}
875 	if (*keyp == '\0')
876 		return 0;
877 	while (linelen > 0 && isspace((unsigned char)keyp[linelen - 1]))
878 		keyp[--linelen] = '\0';
879 	if (*keyp == '#') {
880 		if (*type == T_NONE) {
881 			keyp++;
882 			while (isspace((unsigned char)*keyp))
883 				++keyp;
884 			if (!strcmp(keyp, "@list"))
885 				*type = T_LIST;
886 		}
887 		return 0;
888 	}
889 
890 	if (*keyp == '[') {
891 		if ((valp = strchr(keyp, ']')) == NULL) {
892 			*malformed = 1;
893 			return (0);
894 		}
895 		valp++;
896 	} else
897 		valp = keyp + strcspn(keyp, " \t:");
898 
899 	if (*type == T_NONE)
900 		*type = (*valp == '\0') ? T_LIST : T_HASH;
901 
902 	if (*type == T_LIST) {
903 		*key = keyp;
904 		return (0);
905 	}
906 
907 	/* T_HASH */
908 	if (*valp != '\0') {
909 		*valp++ = '\0';
910 		valp += strspn(valp, " \t");
911 	}
912 	if (*valp == '\0')
913 		*malformed = 1;
914 
915 	*key = keyp;
916 	*val = valp;
917 	return (0);
918 }
919