xref: /openbsd-src/usr.sbin/smtpd/util.c (revision 4c1e55dc91edd6e69ccc60ce855900fbc12cf34f)
1 /*	$OpenBSD: util.c,v 1.66 2012/07/12 08:51:43 chl Exp $	*/
2 
3 /*
4  * Copyright (c) 2000,2001 Markus Friedl.  All rights reserved.
5  * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org>
6  * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net>
7  *
8  * Permission to use, copy, modify, and distribute this software for any
9  * purpose with or without fee is hereby granted, provided that the above
10  * copyright notice and this permission notice appear in all copies.
11  *
12  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19  */
20 
21 /* the mkdir_p() function is based on bin/mkdir/mkdir.c that is covered
22  * by the following license: */
23 /*
24  * Copyright (c) 1983, 1992, 1993
25  *	The Regents of the University of California.  All rights reserved.
26  *
27  * Redistribution and use in source and binary forms, with or without
28  * modification, are permitted provided that the following conditions
29  * are met:
30  * 1. Redistributions of source code must retain the above copyright
31  *    notice, this list of conditions and the following disclaimer.
32  * 2. Redistributions in binary form must reproduce the above copyright
33  *    notice, this list of conditions and the following disclaimer in the
34  *    documentation and/or other materials provided with the distribution.
35  * 3. Neither the name of the University nor the names of its contributors
36  *    may be used to endorse or promote products derived from this software
37  *    without specific prior written permission.
38  *
39  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
40  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
41  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
42  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
43  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
44  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
45  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
46  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
47  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
48  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
49  * SUCH DAMAGE.
50  */
51 
52 #include <sys/types.h>
53 #include <sys/param.h>
54 #include <sys/queue.h>
55 #include <sys/tree.h>
56 #include <sys/socket.h>
57 #include <sys/stat.h>
58 #include <sys/resource.h>
59 
60 #include <netinet/in.h>
61 #include <arpa/inet.h>
62 
63 #include <ctype.h>
64 #include <err.h>
65 #include <errno.h>
66 #include <event.h>
67 #include <fcntl.h>
68 #include <fts.h>
69 #include <imsg.h>
70 #include <libgen.h>
71 #include <netdb.h>
72 #include <pwd.h>
73 #include <stdarg.h>
74 #include <stdio.h>
75 #include <stdlib.h>
76 #include <string.h>
77 #include <time.h>
78 #include <unistd.h>
79 
80 #include "smtpd.h"
81 #include "log.h"
82 
83 const char *log_in6addr(const struct in6_addr *);
84 const char *log_sockaddr(struct sockaddr *);
85 
86 static int temp_inet_net_pton_ipv6(const char *, void *, size_t);
87 
88 int
89 bsnprintf(char *str, size_t size, const char *format, ...)
90 {
91 	int ret;
92 	va_list ap;
93 
94 	va_start(ap, format);
95 	ret = vsnprintf(str, size, format, ap);
96 	va_end(ap);
97 	if (ret == -1 || ret >= (int)size)
98 		return 0;
99 
100 	return 1;
101 }
102 
103 /*
104  * mkdir -p. Based on bin/mkdir/mkdir.c:mkpath()
105  */
106 int
107 mkdir_p(char *path, mode_t mode)
108 {
109 	struct stat	 sb;
110 	char		*slash;
111 	int		 done, exists;
112 	mode_t		 dir_mode;
113 
114 	dir_mode = mode | S_IWUSR | S_IXUSR;
115 
116 	slash = path;
117 
118 	for (;;) {
119 		slash += strspn(slash, "/");
120 		slash += strcspn(slash, "/");
121 
122 		done = (*slash == '\0');
123 		*slash = '\0';
124 
125 		/* skip existing path components */
126 		exists = !stat(path, &sb);
127 		if (!done && exists && S_ISDIR(sb.st_mode)) {
128 			*slash = '/';
129 			continue;
130 		}
131 
132 		if (mkdir(path, done ? mode : dir_mode) == 0) {
133 			if (mode > 0777 && chmod(path, mode) < 0)
134 				return (-1);
135 		} else {
136 			if (!exists) {
137 				/* Not there */
138 				return (-1);
139 			}
140 			if (!S_ISDIR(sb.st_mode)) {
141 				/* Is there, but isn't a directory */
142 				errno = ENOTDIR;
143 				return (-1);
144 			}
145 		}
146 
147 		if (done)
148 			break;
149 
150 		*slash = '/';
151 	}
152 
153 	return (0);
154 }
155 
156 int
157 ckdir(const char *path, mode_t mode, uid_t owner, gid_t group, int create)
158 {
159 	char		mode_str[12];
160 	int		ret;
161 	struct stat	sb;
162 
163 	if (stat(path, &sb) == -1) {
164 		if (errno != ENOENT || create == 0) {
165 			warn("stat: %s", path);
166 			return (0);
167 		}
168 
169 		/* chmod is deferred to avoid umask effect */
170 		if (mkdir(path, 0) == -1) {
171 			warn("mkdir: %s", path);
172 			return (0);
173 		}
174 
175 		if (chown(path, owner, group) == -1) {
176 			warn("chown: %s", path);
177 			return (0);
178 		}
179 
180 		if (chmod(path, mode) == -1) {
181 			warn("chmod: %s", path);
182 			return (0);
183 		}
184 
185 		if (stat(path, &sb) == -1) {
186 			warn("stat: %s", path);
187 			return (0);
188 		}
189 	}
190 
191 	ret = 1;
192 
193 	/* check if it's a directory */
194 	if (!S_ISDIR(sb.st_mode)) {
195 		ret = 0;
196 		warnx("%s is not a directory", path);
197 	}
198 
199 	/* check that it is owned by owner/group */
200 	if (sb.st_uid != owner) {
201 		ret = 0;
202 		warnx("%s is not owned by uid %d", path, owner);
203 	}
204 	if (sb.st_gid != group) {
205 		ret = 0;
206 		warnx("%s is not owned by gid %d", path, group);
207 	}
208 
209 	/* check permission */
210 	if ((sb.st_mode & 07777) != mode) {
211 		ret = 0;
212 		strmode(mode, mode_str);
213 		mode_str[10] = '\0';
214 		warnx("%s must be %s (%o)", path, mode_str + 1, mode);
215 	}
216 
217 	return ret;
218 }
219 
220 int
221 rmtree(char *path, int keepdir)
222 {
223 	char		*path_argv[2];
224 	FTS		*fts;
225 	FTSENT		*e;
226 	int		 ret, depth;
227 
228 	path_argv[0] = path;
229 	path_argv[1] = NULL;
230 	ret = 0;
231 	depth = 1;
232 
233 	if ((fts = fts_open(path_argv, FTS_PHYSICAL, NULL)) == NULL) {
234 		warn("fts_open: %s", path);
235 		return (-1);
236 	}
237 
238 	while ((e = fts_read(fts)) != NULL) {
239 		if (e->fts_number) {
240 			depth--;
241 			if (keepdir && e->fts_number == 1)
242 				continue;
243 			if (rmdir(e->fts_path) == -1) {
244 				warn("rmdir: %s", e->fts_path);
245 				ret = -1;
246 			}
247 			continue;
248 		}
249 
250 		if (S_ISDIR(e->fts_statp->st_mode)) {
251 			e->fts_number = depth++;
252 			continue;
253 		}
254 
255 		if (unlink(e->fts_path) == -1) {
256 			warn("unlink: %s", e->fts_path);
257 			ret = -1;
258 		}
259 	}
260 
261 	fts_close(fts);
262 
263 	return (ret);
264 }
265 
266 int
267 mvpurge(char *from, char *to)
268 {
269 	size_t		 n;
270 	int		 retry;
271 	const char	*sep;
272 	char		 buf[MAXPATHLEN];
273 
274 	if ((n = strlen(to)) == 0)
275 		fatalx("to is empty");
276 
277 	sep = (to[n - 1] == '/') ? "" : "/";
278 	retry = 0;
279 
280     again:
281 	snprintf(buf, sizeof buf, "%s%s%u", to, sep, arc4random());
282 	if (rename(from, buf) == -1) {
283 		/* ENOTDIR has actually 2 meanings, and incorrect input
284 		 * could lead to an infinite loop. Consider that after
285 		 * 20 tries something is hopelessly wrong.
286 		 */
287 		if (errno == ENOTEMPTY || errno == EISDIR || errno == ENOTDIR) {
288 			if ((retry++) >= 20)
289 				return (-1);
290 			goto again;
291 		}
292 		return -1;
293 	}
294 
295 	return 0;
296 }
297 
298 
299 /* Close file, signifying temporary error condition (if any) to the caller. */
300 int
301 safe_fclose(FILE *fp)
302 {
303 	if (ferror(fp)) {
304 		fclose(fp);
305 		return 0;
306 	}
307 	if (fflush(fp)) {
308 		fclose(fp);
309 		if (errno == ENOSPC)
310 			return 0;
311 		fatal("safe_fclose: fflush");
312 	}
313 	if (fsync(fileno(fp)))
314 		fatal("safe_fclose: fsync");
315 	if (fclose(fp))
316 		fatal("safe_fclose: fclose");
317 
318 	return 1;
319 }
320 
321 int
322 hostname_match(char *hostname, char *pattern)
323 {
324 	while (*pattern != '\0' && *hostname != '\0') {
325 		if (*pattern == '*') {
326 			while (*pattern == '*')
327 				pattern++;
328 			while (*hostname != '\0' &&
329 			    tolower((int)*hostname) != tolower((int)*pattern))
330 				hostname++;
331 			continue;
332 		}
333 
334 		if (tolower((int)*pattern) != tolower((int)*hostname))
335 			return 0;
336 		pattern++;
337 		hostname++;
338 	}
339 
340 	return (*hostname == '\0' && *pattern == '\0');
341 }
342 
343 int
344 valid_localpart(const char *s)
345 {
346 /*
347  * RFC 5322 defines theses characters as valid: !#$%&'*+-/=?^_`{|}~
348  * some of them are potentially dangerous, and not so used after all.
349  */
350 #define IS_ATEXT(c)     (isalnum((int)(c)) || strchr("%+-=_", (c)))
351 nextatom:
352         if (! IS_ATEXT(*s) || *s == '\0')
353                 return 0;
354         while (*(++s) != '\0') {
355                 if (*s == '.')
356                         break;
357                 if (IS_ATEXT(*s))
358                         continue;
359                 return 0;
360         }
361         if (*s == '.') {
362                 s++;
363                 goto nextatom;
364         }
365         return 1;
366 }
367 
368 int
369 valid_domainpart(const char *s)
370 {
371 	struct in_addr	 ina;
372 	struct in6_addr	 ina6;
373 	char		*c, domain[MAX_DOMAINPART_SIZE];
374 
375 	if (*s == '[') {
376 		strlcpy(domain, s + 1, sizeof domain);
377 
378 		c = strchr(domain, (int)']');
379 		if (!c || c[1] != '\0')
380 			return 0;
381 
382 		*c = '\0';
383 
384 		if (inet_pton(AF_INET6, domain, &ina6) == 1)
385 			return 1;
386 		if (inet_pton(AF_INET, domain, &ina) == 1)
387 			return 1;
388 
389 		return 0;
390 	}
391 
392 nextsub:
393         if (!isalnum((int)*s))
394                 return 0;
395         while (*(++s) != '\0') {
396                 if (*s == '.')
397                         break;
398                 if (isalnum((int)*s) || *s == '-')
399                         continue;
400                 return 0;
401         }
402         if (s[-1] == '-')
403                 return 0;
404         if (*s == '.') {
405 		s++;
406                 goto nextsub;
407 	}
408         return 1;
409 }
410 
411 int
412 email_to_mailaddr(struct mailaddr *maddr, char *email)
413 {
414 	char *username;
415 	char *hostname;
416 
417 	username = email;
418 	hostname = strrchr(username, '@');
419 
420 	if (username[0] == '\0') {
421 		*maddr->user = '\0';
422 		*maddr->domain = '\0';
423 		return 1;
424 	}
425 
426 	if (hostname == NULL) {
427 		if (strcasecmp(username, "postmaster") != 0)
428 			return 0;
429 		hostname = "localhost";
430 	} else {
431 		*hostname++ = '\0';
432 	}
433 
434 	if (strlcpy(maddr->user, username, sizeof(maddr->user))
435 	    >= sizeof(maddr->user))
436 		return 0;
437 
438 	if (strlcpy(maddr->domain, hostname, sizeof(maddr->domain))
439 	    >= sizeof(maddr->domain))
440 		return 0;
441 
442 	return 1;
443 }
444 
445 char *
446 ss_to_text(struct sockaddr_storage *ss)
447 {
448 	static char	 buf[NI_MAXHOST + 5];
449 	char		*p;
450 
451 	buf[0] = '\0';
452 	p = buf;
453 
454 	if (ss->ss_family == PF_INET) {
455 		in_addr_t addr;
456 
457 		addr = ((struct sockaddr_in *)ss)->sin_addr.s_addr;
458                 addr = ntohl(addr);
459                 bsnprintf(p, NI_MAXHOST,
460                     "%d.%d.%d.%d",
461                     (addr >> 24) & 0xff,
462                     (addr >> 16) & 0xff,
463                     (addr >> 8) & 0xff,
464                     addr & 0xff);
465 	}
466 
467 	if (ss->ss_family == PF_INET6) {
468 		struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)ss;
469 		struct in6_addr	*in6_addr;
470 
471 		strlcpy(buf, "IPv6:", sizeof(buf));
472 		p = buf + 5;
473 		in6_addr = &in6->sin6_addr;
474 		bsnprintf(p, NI_MAXHOST, "%s", log_in6addr(in6_addr));
475 	}
476 
477 	return (buf);
478 }
479 
480 char *
481 time_to_text(time_t when)
482 {
483 	struct tm *lt;
484 	static char buf[40];
485 	char *day[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
486 	char *month[] = {"Jan","Feb","Mar","Apr","May","Jun",
487 		       "Jul","Aug","Sep","Oct","Nov","Dec"};
488 
489 	lt = localtime(&when);
490 	if (lt == NULL || when == 0)
491 		fatalx("time_to_text: localtime");
492 
493 	/* We do not use strftime because it is subject to locale substitution*/
494 	if (! bsnprintf(buf, sizeof(buf), "%s, %d %s %d %02d:%02d:%02d %c%02d%02d (%s)",
495 		day[lt->tm_wday], lt->tm_mday, month[lt->tm_mon],
496 		lt->tm_year + 1900,
497 		lt->tm_hour, lt->tm_min, lt->tm_sec,
498 		lt->tm_gmtoff >= 0 ? '+' : '-',
499 		abs((int)lt->tm_gmtoff / 3600),
500 		abs((int)lt->tm_gmtoff % 3600) / 60,
501 		lt->tm_zone))
502 		fatalx("time_to_text: bsnprintf");
503 
504 	return buf;
505 }
506 
507 int
508 text_to_netaddr(struct netaddr *netaddr, char *s)
509 {
510 	struct sockaddr_storage	ss;
511 	struct sockaddr_in	ssin;
512 	struct sockaddr_in6	ssin6;
513 	int			bits;
514 
515 	if (strncmp("IPv6:", s, 5) == 0)
516 		s += 5;
517 
518 	if (strchr(s, '/') != NULL) {
519 		/* dealing with netmask */
520 
521 		bzero(&ssin, sizeof(struct sockaddr_in));
522 		bits = inet_net_pton(AF_INET, s, &ssin.sin_addr,
523 		    sizeof(struct in_addr));
524 
525 		if (bits != -1) {
526 			ssin.sin_family = AF_INET;
527 			memcpy(&ss, &ssin, sizeof(ssin));
528 			ss.ss_len = sizeof(struct sockaddr_in);
529 		}
530 		else {
531 			bzero(&ssin6, sizeof(struct sockaddr_in6));
532 			bits = inet_net_pton(AF_INET6, s, &ssin6.sin6_addr,
533 			    sizeof(struct in6_addr));
534 			if (bits == -1) {
535 
536 				/* XXX - until AF_INET6 support gets in base */
537 				if (errno != EAFNOSUPPORT) {
538 					log_warn("inet_net_pton");
539 					return 0;
540 				}
541 				bits = temp_inet_net_pton_ipv6(s,
542 				    &ssin6.sin6_addr,
543 				    sizeof(struct in6_addr));
544 			}
545 			if (bits == -1) {
546 				log_warn("inet_net_pton");
547 				return 0;
548 			}
549 			ssin6.sin6_family = AF_INET6;
550 			memcpy(&ss, &ssin6, sizeof(ssin6));
551 			ss.ss_len = sizeof(struct sockaddr_in6);
552 		}
553 	}
554 	else {
555 		/* IP address ? */
556 		if (inet_pton(AF_INET, s, &ssin.sin_addr) == 1) {
557 			ssin.sin_family = AF_INET;
558 			bits = 32;
559 			memcpy(&ss, &ssin, sizeof(ssin));
560 			ss.ss_len = sizeof(struct sockaddr_in);
561 		}
562 		else if (inet_pton(AF_INET6, s, &ssin6.sin6_addr) == 1) {
563 			ssin6.sin6_family = AF_INET6;
564 			bits = 128;
565 			memcpy(&ss, &ssin6, sizeof(ssin6));
566 			ss.ss_len = sizeof(struct sockaddr_in6);
567 		}
568 		else return 0;
569 	}
570 
571 	netaddr->ss   = ss;
572 	netaddr->bits = bits;
573 	return 1;
574 }
575 
576 int
577 text_to_relayhost(struct relayhost *relay, char *s)
578 {
579 	u_int32_t		 i;
580 	struct schema {
581 		char		*name;
582 		u_int8_t	 flags;
583 	} schemas [] = {
584 		{ "smtp://",		0				},
585 		{ "smtps://",		F_SMTPS				},
586 		{ "tls://",		F_STARTTLS			},
587 		{ "smtps+auth://",     	F_SMTPS|F_AUTH			},
588 		{ "tls+auth://",	F_STARTTLS|F_AUTH		},
589 		{ "ssl://",		F_SMTPS|F_STARTTLS		},
590 		{ "ssl+auth://",	F_SMTPS|F_STARTTLS|F_AUTH	}
591 	};
592 	const char	*errstr = NULL;
593 	char	*p;
594 	char	*sep;
595 	int	 len;
596 
597 	for (i = 0; i < nitems(schemas); ++i)
598 		if (strncasecmp(schemas[i].name, s, strlen(schemas[i].name)) == 0)
599 			break;
600 
601 	if (i == nitems(schemas)) {
602 		/* there is a schema, but it's not recognized */
603 		if (strstr(s, "://"))
604 			return 0;
605 
606 		/* no schema, default to smtp:// */
607 		i = 0;
608 		p = s;
609 	}
610 	else
611 		p = s + strlen(schemas[i].name);
612 
613 	relay->flags = schemas[i].flags;
614 
615 	if ((sep = strrchr(p, ':')) != NULL) {
616 		relay->port = strtonum(sep+1, 1, 0xffff, &errstr);
617 		if (errstr)
618 			return 0;
619 		len = sep - p;
620 	}
621 	else
622 		len = strlen(p);
623 
624 	if (strlcpy(relay->hostname, p, sizeof (relay->hostname))
625 	    >= sizeof (relay->hostname))
626 		return 0;
627 
628 	relay->hostname[len] = 0;
629 
630 	return 1;
631 }
632 
633 /*
634  * Check file for security. Based on usr.bin/ssh/auth.c.
635  */
636 int
637 secure_file(int fd, char *path, char *userdir, uid_t uid, int mayread)
638 {
639 	char		 buf[MAXPATHLEN];
640 	char		 homedir[MAXPATHLEN];
641 	struct stat	 st;
642 	char		*cp;
643 
644 	if (realpath(path, buf) == NULL)
645 		return 0;
646 
647 	if (realpath(userdir, homedir) == NULL)
648 		homedir[0] = '\0';
649 
650 	/* Check the open file to avoid races. */
651 	if (fstat(fd, &st) < 0 ||
652 	    !S_ISREG(st.st_mode) ||
653 	    (st.st_uid != 0 && st.st_uid != uid) ||
654 	    (st.st_mode & (mayread ? 022 : 066)) != 0)
655 		return 0;
656 
657 	/* For each component of the canonical path, walking upwards. */
658 	for (;;) {
659 		if ((cp = dirname(buf)) == NULL)
660 			return 0;
661 		strlcpy(buf, cp, sizeof(buf));
662 
663 		if (stat(buf, &st) < 0 ||
664 		    (st.st_uid != 0 && st.st_uid != uid) ||
665 		    (st.st_mode & 022) != 0)
666 			return 0;
667 
668 		/* We can stop checking after reaching homedir level. */
669 		if (strcmp(homedir, buf) == 0)
670 			break;
671 
672 		/*
673 		 * dirname should always complete with a "/" path,
674 		 * but we can be paranoid and check for "." too
675 		 */
676 		if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0))
677 			break;
678 	}
679 
680 	return 1;
681 }
682 
683 void
684 addargs(arglist *args, char *fmt, ...)
685 {
686 	va_list ap;
687 	char *cp;
688 	u_int nalloc;
689 	int r;
690 
691 	va_start(ap, fmt);
692 	r = vasprintf(&cp, fmt, ap);
693 	va_end(ap);
694 	if (r == -1)
695 		fatal("addargs: argument too long");
696 
697 	nalloc = args->nalloc;
698 	if (args->list == NULL) {
699 		nalloc = 32;
700 		args->num = 0;
701 	} else if (args->num+2 >= nalloc)
702 		nalloc *= 2;
703 
704 	if (SIZE_T_MAX / nalloc < sizeof(char *))
705 		fatalx("addargs: nalloc * size > SIZE_T_MAX");
706 	args->list = realloc(args->list, nalloc * sizeof(char *));
707 	if (args->list == NULL)
708 		fatal("addargs: realloc");
709 	args->nalloc = nalloc;
710 	args->list[args->num++] = cp;
711 	args->list[args->num] = NULL;
712 }
713 
714 void
715 lowercase(char *buf, char *s, size_t len)
716 {
717 	if (len == 0)
718 		fatalx("lowercase: len == 0");
719 
720 	if (strlcpy(buf, s, len) >= len)
721 		fatalx("lowercase: truncation");
722 
723 	while (*buf != '\0') {
724 		*buf = tolower((int)*buf);
725 		buf++;
726 	}
727 }
728 
729 void
730 sa_set_port(struct sockaddr *sa, int port)
731 {
732 	char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
733 	struct addrinfo hints, *res;
734 	int error;
735 
736 	error = getnameinfo(sa, sa->sa_len, hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST);
737 	if (error)
738 		fatalx("sa_set_port: getnameinfo failed");
739 
740 	memset(&hints, 0, sizeof(hints));
741 	hints.ai_family = PF_UNSPEC;
742 	hints.ai_socktype = SOCK_STREAM;
743 	hints.ai_flags = AI_NUMERICHOST|AI_NUMERICSERV;
744 
745 	snprintf(sbuf, sizeof(sbuf), "%d", port);
746 
747 	error = getaddrinfo(hbuf, sbuf, &hints, &res);
748 	if (error)
749 		fatalx("sa_set_port: getaddrinfo failed");
750 
751 	memcpy(sa, res->ai_addr, res->ai_addrlen);
752 	freeaddrinfo(res);
753 }
754 
755 u_int64_t
756 generate_uid(void)
757 {
758 	static u_int32_t id = 0;
759 
760 	return ((uint64_t)(id++) << 32 | arc4random());
761 }
762 
763 void
764 fdlimit(double percent)
765 {
766 	struct rlimit rl;
767 
768 	if (percent < 0 || percent > 1)
769 		fatalx("fdlimit: parameter out of range");
770 	if (getrlimit(RLIMIT_NOFILE, &rl) == -1)
771 		fatal("fdlimit: getrlimit");
772 	rl.rlim_cur = percent * rl.rlim_max;
773 	if (setrlimit(RLIMIT_NOFILE, &rl) == -1)
774 		fatal("fdlimit: setrlimit");
775 }
776 
777 int
778 availdesc(void)
779 {
780 	int avail;
781 
782 	avail = getdtablesize();
783 	avail -= 3;		/* stdin, stdout, stderr */
784 	avail -= PROC_COUNT;	/* imsg channels */
785 	avail -= 5;		/* safety buffer */
786 
787 	return (avail);
788 }
789 
790 void
791 session_socket_blockmode(int fd, enum blockmodes bm)
792 {
793 	int	flags;
794 
795 	if ((flags = fcntl(fd, F_GETFL, 0)) == -1)
796 		fatal("fcntl F_GETFL");
797 
798 	if (bm == BM_NONBLOCK)
799 		flags |= O_NONBLOCK;
800 	else
801 		flags &= ~O_NONBLOCK;
802 
803 	if ((flags = fcntl(fd, F_SETFL, flags)) == -1)
804 		fatal("fcntl F_SETFL");
805 }
806 
807 void
808 session_socket_no_linger(int fd)
809 {
810 	struct linger	 lng;
811 
812 	bzero(&lng, sizeof(lng));
813 	if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &lng, sizeof(lng)) == -1)
814 		fatal("session_socket_no_linger");
815 }
816 
817 int
818 session_socket_error(int fd)
819 {
820 	int		error;
821 	socklen_t	len;
822 
823 	len = sizeof(error);
824 	if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) == -1)
825 		fatal("session_socket_error: getsockopt");
826 
827 	return (error);
828 }
829 
830 const char *
831 log_in6addr(const struct in6_addr *addr)
832 {
833 	struct sockaddr_in6	sa_in6;
834 	u_int16_t		tmp16;
835 
836 	bzero(&sa_in6, sizeof(sa_in6));
837 	sa_in6.sin6_len = sizeof(sa_in6);
838 	sa_in6.sin6_family = AF_INET6;
839 	memcpy(&sa_in6.sin6_addr, addr, sizeof(sa_in6.sin6_addr));
840 
841 	/* XXX thanks, KAME, for this ugliness... adopted from route/show.c */
842 	if (IN6_IS_ADDR_LINKLOCAL(&sa_in6.sin6_addr) ||
843 	    IN6_IS_ADDR_MC_LINKLOCAL(&sa_in6.sin6_addr)) {
844 		memcpy(&tmp16, &sa_in6.sin6_addr.s6_addr[2], sizeof(tmp16));
845 		sa_in6.sin6_scope_id = ntohs(tmp16);
846 		sa_in6.sin6_addr.s6_addr[2] = 0;
847 		sa_in6.sin6_addr.s6_addr[3] = 0;
848 	}
849 
850 	return (log_sockaddr((struct sockaddr *)&sa_in6));
851 }
852 
853 const char *
854 log_sockaddr(struct sockaddr *sa)
855 {
856 	static char	buf[NI_MAXHOST];
857 
858 	if (getnameinfo(sa, sa->sa_len, buf, sizeof(buf), NULL, 0,
859 	    NI_NUMERICHOST))
860 		return ("(unknown)");
861 	else
862 		return (buf);
863 }
864 
865 u_int32_t
866 evpid_to_msgid(u_int64_t evpid)
867 {
868 	return (evpid >> 32);
869 }
870 
871 u_int64_t
872 msgid_to_evpid(u_int32_t msgid)
873 {
874 	return ((u_int64_t)msgid << 32);
875 }
876 
877 const char *
878 parse_smtp_response(char *line, size_t len, char **msg, int *cont)
879 {
880 	size_t	 i;
881 
882 	if (len >= SMTP_LINE_MAX)
883 		return "line too long";
884 
885 	if (len > 3) {
886 		if (msg)
887 			*msg = line + 4;
888 		if (cont)
889 			*cont = (line[3] == '-');
890 	} else if (len == 3) {
891 		if (msg)
892 			*msg = line + 3;
893 		if (cont)
894 			*cont = 0;
895 	} else
896 		return "line too short";
897 
898 	/* validate reply code */
899 	if (line[0] < '2' || line[0] > '5' || !isdigit(line[1]) ||
900 	    !isdigit(line[2]))
901 		return "reply code out of range";
902 
903 	/* validate reply message */
904 	for (i = 0; i < len; i++)
905 		if (!isprint(line[i]))
906 			return "non-printable character in reply";
907 
908 	return NULL;
909 }
910 
911 static int
912 temp_inet_net_pton_ipv6(const char *src, void *dst, size_t size)
913 {
914 	int	ret;
915 	int	bits;
916 	char	buf[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255:255:255:255/128")];
917 	char		*sep;
918 	const char	*errstr;
919 
920 	if (strlcpy(buf, src, sizeof buf) >= sizeof buf) {
921 		errno = EMSGSIZE;
922 		return (-1);
923 	}
924 
925 	sep = strchr(buf, '/');
926 	if (sep != NULL)
927 		*sep++ = '\0';
928 
929 	ret = inet_pton(AF_INET6, buf, dst);
930 	if (ret != 1)
931 		return (-1);
932 
933 	if (sep == NULL)
934 		return 128;
935 
936 	bits = strtonum(sep, 0, 128, &errstr);
937 	if (errstr)
938 		return (-1);
939 
940 	return bits;
941 }
942