xref: /openbsd/usr.sbin/smtpd/util.c (revision 3d8817e4)
1 /*	$OpenBSD: util.c,v 1.44 2011/04/17 13:36:07 gilles 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 #include <sys/types.h>
22 #include <sys/param.h>
23 #include <sys/queue.h>
24 #include <sys/tree.h>
25 #include <sys/socket.h>
26 #include <sys/stat.h>
27 #include <sys/resource.h>
28 
29 #include <netinet/in.h>
30 
31 #include <ctype.h>
32 #include <err.h>
33 #include <errno.h>
34 #include <event.h>
35 #include <fcntl.h>
36 #include <imsg.h>
37 #include <libgen.h>
38 #include <netdb.h>
39 #include <pwd.h>
40 #include <stdarg.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <time.h>
45 #include <unistd.h>
46 
47 #include "smtpd.h"
48 #include "log.h"
49 
50 const char *log_in6addr(const struct in6_addr *);
51 const char *log_sockaddr(struct sockaddr *);
52 
53 int
54 bsnprintf(char *str, size_t size, const char *format, ...)
55 {
56 	int ret;
57 	va_list ap;
58 
59 	va_start(ap, format);
60 	ret = vsnprintf(str, size, format, ap);
61 	va_end(ap);
62 	if (ret == -1 || ret >= (int)size)
63 		return 0;
64 
65 	return 1;
66 }
67 
68 /* Close file, signifying temporary error condition (if any) to the caller. */
69 int
70 safe_fclose(FILE *fp)
71 {
72 	if (ferror(fp)) {
73 		fclose(fp);
74 		return 0;
75 	}
76 	if (fflush(fp)) {
77 		fclose(fp);
78 		if (errno == ENOSPC)
79 			return 0;
80 		fatal("safe_fclose: fflush");
81 	}
82 	if (fsync(fileno(fp)))
83 		fatal("safe_fclose: fsync");
84 	if (fclose(fp))
85 		fatal("safe_fclose: fclose");
86 
87 	return 1;
88 }
89 
90 int
91 hostname_match(char *hostname, char *pattern)
92 {
93 	while (*pattern != '\0' && *hostname != '\0') {
94 		if (*pattern == '*') {
95 			while (*pattern == '*')
96 				pattern++;
97 			while (*hostname != '\0' &&
98 			    tolower((int)*hostname) != tolower((int)*pattern))
99 				hostname++;
100 			continue;
101 		}
102 
103 		if (tolower((int)*pattern) != tolower((int)*hostname))
104 			return 0;
105 		pattern++;
106 		hostname++;
107 	}
108 
109 	return (*hostname == '\0' && *pattern == '\0');
110 }
111 
112 int
113 recipient_to_path(struct path *path, char *recipient)
114 {
115 	char *username;
116 	char *hostname;
117 
118 	username = recipient;
119 	hostname = strrchr(username, '@');
120 
121 	if (username[0] == '\0') {
122 		*path->user = '\0';
123 		*path->domain = '\0';
124 		return 1;
125 	}
126 
127 	if (hostname == NULL) {
128 		if (strcasecmp(username, "postmaster") != 0)
129 			return 0;
130 		hostname = "localhost";
131 	} else {
132 		*hostname++ = '\0';
133 	}
134 
135 	if (strlcpy(path->user, username, sizeof(path->user))
136 	    >= sizeof(path->user))
137 		return 0;
138 
139 	if (strlcpy(path->domain, hostname, sizeof(path->domain))
140 	    >= sizeof(path->domain))
141 		return 0;
142 
143 	return 1;
144 }
145 
146 int
147 valid_localpart(char *s)
148 {
149 #define IS_ATEXT(c)     (isalnum((int)(c)) || strchr("!#$%&'*+-/=?^_`{|}~", (c)))
150 nextatom:
151         if (! IS_ATEXT(*s) || *s == '\0')
152                 return 0;
153         while (*(++s) != '\0') {
154                 if (*s == '.')
155                         break;
156                 if (IS_ATEXT(*s))
157                         continue;
158                 return 0;
159         }
160         if (*s == '.') {
161                 s++;
162                 goto nextatom;
163         }
164         return 1;
165 }
166 
167 int
168 valid_domainpart(char *s)
169 {
170 nextsub:
171         if (!isalnum((int)*s))
172                 return 0;
173         while (*(++s) != '\0') {
174                 if (*s == '.')
175                         break;
176                 if (isalnum((int)*s) || *s == '-')
177                         continue;
178                 return 0;
179         }
180         if (s[-1] == '-')
181                 return 0;
182         if (*s == '.') {
183 		s++;
184                 goto nextsub;
185 	}
186         return 1;
187 }
188 
189 char *
190 ss_to_text(struct sockaddr_storage *ss)
191 {
192 	static char	 buf[NI_MAXHOST + 5];
193 	char		*p;
194 
195 	buf[0] = '\0';
196 	p = buf;
197 
198 	if (ss->ss_family == PF_INET) {
199 		in_addr_t addr;
200 
201 		addr = ((struct sockaddr_in *)ss)->sin_addr.s_addr;
202                 addr = ntohl(addr);
203                 bsnprintf(p, NI_MAXHOST,
204                     "%d.%d.%d.%d",
205                     (addr >> 24) & 0xff,
206                     (addr >> 16) & 0xff,
207                     (addr >> 8) & 0xff,
208                     addr & 0xff);
209 	}
210 
211 	if (ss->ss_family == PF_INET6) {
212 		struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)ss;
213 		struct in6_addr	*in6_addr;
214 
215 		strlcpy(buf, "IPv6:", sizeof(buf));
216 		p = buf + 5;
217 		in6_addr = &in6->sin6_addr;
218 		bsnprintf(p, NI_MAXHOST, "%s", log_in6addr(in6_addr));
219 	}
220 
221 	return (buf);
222 }
223 
224 char *
225 time_to_text(time_t when)
226 {
227 	struct tm *lt;
228 	static char buf[40];
229 	char *day[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
230 	char *month[] = {"Jan","Feb","Mar","Apr","May","Jun",
231 		       "Jul","Aug","Sep","Oct","Nov","Dec"};
232 
233 	lt = localtime(&when);
234 	if (lt == NULL || when == 0)
235 		fatalx("time_to_text: localtime");
236 
237 	/* We do not use strftime because it is subject to locale substitution*/
238 	if (! bsnprintf(buf, sizeof(buf), "%s, %d %s %d %02d:%02d:%02d %c%02d%02d (%s)",
239 		day[lt->tm_wday], lt->tm_mday, month[lt->tm_mon],
240 		lt->tm_year + 1900,
241 		lt->tm_hour, lt->tm_min, lt->tm_sec,
242 		lt->tm_gmtoff >= 0 ? '+' : '-',
243 		abs((int)lt->tm_gmtoff / 3600),
244 		abs((int)lt->tm_gmtoff % 3600) / 60,
245 		lt->tm_zone))
246 		fatalx("time_to_text: bsnprintf");
247 
248 	return buf;
249 }
250 
251 /*
252  * Check file for security. Based on usr.bin/ssh/auth.c.
253  */
254 int
255 secure_file(int fd, char *path, struct passwd *pw, int mayread)
256 {
257 	char		 buf[MAXPATHLEN];
258 	char		 homedir[MAXPATHLEN];
259 	struct stat	 st;
260 	char		*cp;
261 
262 	if (realpath(path, buf) == NULL)
263 		return 0;
264 
265 	if (realpath(pw->pw_dir, homedir) == NULL)
266 		homedir[0] = '\0';
267 
268 	/* Check the open file to avoid races. */
269 	if (fstat(fd, &st) < 0 ||
270 	    !S_ISREG(st.st_mode) ||
271 	    (st.st_uid != 0 && st.st_uid != pw->pw_uid) ||
272 	    (st.st_mode & (mayread ? 022 : 066)) != 0)
273 		return 0;
274 
275 	/* For each component of the canonical path, walking upwards. */
276 	for (;;) {
277 		if ((cp = dirname(buf)) == NULL)
278 			return 0;
279 		strlcpy(buf, cp, sizeof(buf));
280 
281 		if (stat(buf, &st) < 0 ||
282 		    (st.st_uid != 0 && st.st_uid != pw->pw_uid) ||
283 		    (st.st_mode & 022) != 0)
284 			return 0;
285 
286 		/* We can stop checking after reaching homedir level. */
287 		if (strcmp(homedir, buf) == 0)
288 			break;
289 
290 		/*
291 		 * dirname should always complete with a "/" path,
292 		 * but we can be paranoid and check for "." too
293 		 */
294 		if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0))
295 			break;
296 	}
297 
298 	return 1;
299 }
300 
301 void
302 addargs(arglist *args, char *fmt, ...)
303 {
304 	va_list ap;
305 	char *cp;
306 	u_int nalloc;
307 	int r;
308 
309 	va_start(ap, fmt);
310 	r = vasprintf(&cp, fmt, ap);
311 	va_end(ap);
312 	if (r == -1)
313 		fatal("addargs: argument too long");
314 
315 	nalloc = args->nalloc;
316 	if (args->list == NULL) {
317 		nalloc = 32;
318 		args->num = 0;
319 	} else if (args->num+2 >= nalloc)
320 		nalloc *= 2;
321 
322 	if (SIZE_T_MAX / nalloc < sizeof(char *))
323 		fatalx("addargs: nalloc * size > SIZE_T_MAX");
324 	args->list = realloc(args->list, nalloc * sizeof(char *));
325 	if (args->list == NULL)
326 		fatal("addargs: realloc");
327 	args->nalloc = nalloc;
328 	args->list[args->num++] = cp;
329 	args->list[args->num] = NULL;
330 }
331 
332 void
333 lowercase(char *buf, char *s, size_t len)
334 {
335 	if (len == 0)
336 		fatalx("lowercase: len == 0");
337 
338 	if (strlcpy(buf, s, len) >= len)
339 		fatalx("lowercase: truncation");
340 
341 	while (*buf != '\0') {
342 		*buf = tolower((int)*buf);
343 		buf++;
344 	}
345 }
346 
347 void
348 message_set_errormsg(struct envelope *m, char *fmt, ...)
349 {
350 	int ret;
351 	va_list ap;
352 
353 	va_start(ap, fmt);
354 
355 	ret = vsnprintf(m->session_errorline, MAX_LINE_SIZE, fmt, ap);
356 	if (ret >= MAX_LINE_SIZE)
357 		strlcpy(m->session_errorline + (MAX_LINE_SIZE - 4), "...", 4);
358 
359 	/* this should not happen */
360 	if (ret == -1)
361 		err(1, "vsnprintf");
362 
363 	va_end(ap);
364 }
365 
366 char *
367 message_get_errormsg(struct envelope *m)
368 {
369 	return m->session_errorline;
370 }
371 
372 void
373 sa_set_port(struct sockaddr *sa, int port)
374 {
375 	char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
376 	struct addrinfo hints, *res;
377 	int error;
378 
379 	error = getnameinfo(sa, sa->sa_len, hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST);
380 	if (error)
381 		fatalx("sa_set_port: getnameinfo failed");
382 
383 	memset(&hints, 0, sizeof(hints));
384 	hints.ai_family = PF_UNSPEC;
385 	hints.ai_socktype = SOCK_STREAM;
386 	hints.ai_flags = AI_NUMERICHOST|AI_NUMERICSERV;
387 
388 	snprintf(sbuf, sizeof(sbuf), "%d", port);
389 
390 	error = getaddrinfo(hbuf, sbuf, &hints, &res);
391 	if (error)
392 		fatalx("sa_set_port: getaddrinfo failed");
393 
394 	memcpy(sa, res->ai_addr, res->ai_addrlen);
395 	freeaddrinfo(res);
396 }
397 
398 struct path *
399 path_dup(struct path *path)
400 {
401 	struct path *pathp;
402 
403 	pathp = calloc(sizeof(struct path), 1);
404 	if (pathp == NULL)
405 		fatal("calloc");
406 
407 	*pathp = *path;
408 
409 	return pathp;
410 }
411 
412 u_int64_t
413 generate_uid(void)
414 {
415 	u_int64_t	id;
416 	struct timeval	tp;
417 
418 	if (gettimeofday(&tp, NULL) == -1)
419 		fatal("generate_uid: time");
420 
421 	id = (u_int32_t)tp.tv_sec;
422 	id <<= 32;
423 	id |= (u_int32_t)tp.tv_usec;
424 	usleep(1);
425 
426 	return (id);
427 }
428 
429 void
430 fdlimit(double percent)
431 {
432 	struct rlimit rl;
433 
434 	if (percent < 0 || percent > 1)
435 		fatalx("fdlimit: parameter out of range");
436 	if (getrlimit(RLIMIT_NOFILE, &rl) == -1)
437 		fatal("fdlimit: getrlimit");
438 	rl.rlim_cur = percent * rl.rlim_max;
439 	if (setrlimit(RLIMIT_NOFILE, &rl) == -1)
440 		fatal("fdlimit: setrlimit");
441 }
442 
443 int
444 availdesc(void)
445 {
446 	int avail;
447 
448 	avail = getdtablesize();
449 	avail -= 3;		/* stdin, stdout, stderr */
450 	avail -= PROC_COUNT;	/* imsg channels */
451 	avail -= 5;		/* safety buffer */
452 
453 	return (avail);
454 }
455 
456 void
457 session_socket_blockmode(int fd, enum blockmodes bm)
458 {
459 	int	flags;
460 
461 	if ((flags = fcntl(fd, F_GETFL, 0)) == -1)
462 		fatal("fcntl F_GETFL");
463 
464 	if (bm == BM_NONBLOCK)
465 		flags |= O_NONBLOCK;
466 	else
467 		flags &= ~O_NONBLOCK;
468 
469 	if ((flags = fcntl(fd, F_SETFL, flags)) == -1)
470 		fatal("fcntl F_SETFL");
471 }
472 
473 void
474 session_socket_no_linger(int fd)
475 {
476 	struct linger	 lng;
477 
478 	bzero(&lng, sizeof(lng));
479 	if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &lng, sizeof(lng)) == -1)
480 		fatal("session_socket_no_linger");
481 }
482 
483 int
484 session_socket_error(int fd)
485 {
486 	int	 error, len;
487 
488 	len = sizeof(error);
489 	if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) == -1)
490 		fatal("session_socket_error: getsockopt");
491 
492 	return (error);
493 }
494 
495 const char *
496 log_in6addr(const struct in6_addr *addr)
497 {
498 	struct sockaddr_in6	sa_in6;
499 	u_int16_t		tmp16;
500 
501 	bzero(&sa_in6, sizeof(sa_in6));
502 	sa_in6.sin6_len = sizeof(sa_in6);
503 	sa_in6.sin6_family = AF_INET6;
504 	memcpy(&sa_in6.sin6_addr, addr, sizeof(sa_in6.sin6_addr));
505 
506 	/* XXX thanks, KAME, for this ugliness... adopted from route/show.c */
507 	if (IN6_IS_ADDR_LINKLOCAL(&sa_in6.sin6_addr) ||
508 	    IN6_IS_ADDR_MC_LINKLOCAL(&sa_in6.sin6_addr)) {
509 		memcpy(&tmp16, &sa_in6.sin6_addr.s6_addr[2], sizeof(tmp16));
510 		sa_in6.sin6_scope_id = ntohs(tmp16);
511 		sa_in6.sin6_addr.s6_addr[2] = 0;
512 		sa_in6.sin6_addr.s6_addr[3] = 0;
513 	}
514 
515 	return (log_sockaddr((struct sockaddr *)&sa_in6));
516 }
517 
518 const char *
519 log_sockaddr(struct sockaddr *sa)
520 {
521 	static char	buf[NI_MAXHOST];
522 
523 	if (getnameinfo(sa, sa->sa_len, buf, sizeof(buf), NULL, 0,
524 	    NI_NUMERICHOST))
525 		return ("(unknown)");
526 	else
527 		return (buf);
528 }
529 
530 u_int32_t
531 filename_to_msgid(char *filename)
532 {
533 	u_int32_t ulval;
534 	char *ep;
535 
536 	errno = 0;
537 	ulval = strtoul(filename, &ep, 16);
538 	if (filename[0] == '\0' || *ep != '\0')
539 		return 0;
540 	if (errno == ERANGE && ulval == 0xffffffff)
541 		return 0;
542 
543 	return ulval;
544 }
545 
546 u_int64_t
547 filename_to_evpid(char *filename)
548 {
549 	u_int64_t ullval;
550 	char *ep;
551 
552 	errno = 0;
553 	ullval = strtoull(filename, &ep, 16);
554 	if (filename[0] == '\0' || *ep != '\0')
555 		return 0;
556 	if (errno == ERANGE && ullval == ULLONG_MAX)
557 		return 0;
558 
559 	return ullval;
560 }
561 
562 u_int32_t
563 evpid_to_msgid(u_int64_t evpid)
564 {
565 	return (evpid >> 32);
566 }
567 
568 u_int64_t
569 msgid_to_evpid(u_int32_t msgid)
570 {
571 	return ((u_int64_t)msgid << 32);
572 }
573