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