xref: /openbsd-src/usr.sbin/smtpd/util.c (revision 43003dfe3ad45d1698bed8a37f2b0f5b14f20d4f)
1 /*	$OpenBSD: util.c,v 1.27 2009/09/15 16:50:06 jacekm 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 
28 #include <err.h>
29 #include <ctype.h>
30 #include <errno.h>
31 #include <event.h>
32 #include <libgen.h>
33 #include <netdb.h>
34 #include <pwd.h>
35 #include <stdarg.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <time.h>
40 #include <unistd.h>
41 
42 #include "smtpd.h"
43 
44 int
45 bsnprintf(char *str, size_t size, const char *format, ...)
46 {
47 	int ret;
48 	va_list ap;
49 
50 	va_start(ap, format);
51 	ret = vsnprintf(str, size, format, ap);
52 	va_end(ap);
53 	if (ret == -1 || ret >= (int)size)
54 		return 0;
55 
56 	return 1;
57 }
58 
59 /* Close file, signifying temporary error condition (if any) to the caller. */
60 int
61 safe_fclose(FILE *fp)
62 {
63 	if (ferror(fp)) {
64 		fclose(fp);
65 		return 0;
66 	}
67 	if (fflush(fp)) {
68 		fclose(fp);
69 		if (errno == ENOSPC)
70 			return 0;
71 		fatal("safe_fclose: fflush");
72 	}
73 	if (fsync(fileno(fp)))
74 		fatal("safe_fclose: fsync");
75 	if (fclose(fp))
76 		fatal("safe_fclose: fclose");
77 
78 	return 1;
79 }
80 
81 int
82 hostname_match(char *hostname, char *pattern)
83 {
84 	while (*pattern != '\0' && *hostname != '\0') {
85 		if (*pattern == '*') {
86 			while (*pattern == '*')
87 				pattern++;
88 			while (*hostname != '\0' &&
89 			    tolower((int)*hostname) != tolower((int)*pattern))
90 				hostname++;
91 			continue;
92 		}
93 
94 		if (tolower((int)*pattern) != tolower((int)*hostname))
95 			return 0;
96 		pattern++;
97 		hostname++;
98 	}
99 
100 	return (*hostname == '\0' && *pattern == '\0');
101 }
102 
103 int
104 recipient_to_path(struct path *path, char *recipient)
105 {
106 	char *username;
107 	char *hostname;
108 
109 	username = recipient;
110 	hostname = strrchr(username, '@');
111 
112 	if (username[0] == '\0') {
113 		*path->user = '\0';
114 		*path->domain = '\0';
115 		return 1;
116 	}
117 
118 	if (hostname == NULL) {
119 		if (strcasecmp(username, "postmaster") != 0)
120 			return 0;
121 		hostname = "localhost";
122 	} else {
123 		*hostname++ = '\0';
124 	}
125 
126 	if (strlcpy(path->user, username, sizeof(path->user))
127 	    >= sizeof(path->user))
128 		return 0;
129 
130 	if (strlcpy(path->domain, hostname, sizeof(path->domain))
131 	    >= sizeof(path->domain))
132 		return 0;
133 
134 	return 1;
135 }
136 
137 int
138 valid_localpart(char *s)
139 {
140 #define IS_ATEXT(c)     (isalnum((int)(c)) || strchr("!#$%&'*+-/=?^_`{|}~", (c)))
141 nextatom:
142         if (! IS_ATEXT(*s) || *s == '\0')
143                 return 0;
144         while (*(++s) != '\0') {
145                 if (*s == '.')
146                         break;
147                 if (IS_ATEXT(*s))
148                         continue;
149                 return 0;
150         }
151         if (*s == '.') {
152                 s++;
153                 goto nextatom;
154         }
155         return 1;
156 }
157 
158 int
159 valid_domainpart(char *s)
160 {
161 nextsub:
162         if (!isalnum((int)*s))
163                 return 0;
164         while (*(++s) != '\0') {
165                 if (*s == '.')
166                         break;
167                 if (isalnum((int)*s) || *s == '-')
168                         continue;
169                 return 0;
170         }
171         if (s[-1] == '-')
172                 return 0;
173         if (*s == '.') {
174 		s++;
175                 goto nextsub;
176 	}
177         return 1;
178 }
179 
180 char *
181 ss_to_text(struct sockaddr_storage *ss)
182 {
183 	static char	 buf[NI_MAXHOST + 5];
184 	char		*p;
185 
186 	buf[0] = '\0';
187 	p = buf;
188 
189 	if (ss->ss_family == PF_INET6) {
190 		strlcpy(buf, "IPv6:", sizeof(buf));
191 		p = buf + 5;
192 	}
193 
194 	if (getnameinfo((struct sockaddr *)ss, ss->ss_len, p,
195 	    NI_MAXHOST, NULL, 0, NI_NUMERICHOST))
196 		fatalx("ss_to_text: getnameinfo");
197 
198 	return (buf);
199 }
200 
201 int
202 valid_message_id(char *mid)
203 {
204 	u_int8_t cnt;
205 
206 	/* [0-9]{10}\.[a-zA-Z0-9]{16} */
207 	for (cnt = 0; cnt < 10; ++cnt, ++mid)
208 		if (! isdigit((int)*mid))
209 			return 0;
210 
211 	if (*mid++ != '.')
212 		return 0;
213 
214 	for (cnt = 0; cnt < 16; ++cnt, ++mid)
215 		if (! isalnum((int)*mid))
216 			return 0;
217 
218 	return (*mid == '\0');
219 }
220 
221 int
222 valid_message_uid(char *muid)
223 {
224 	u_int8_t cnt;
225 
226 	/* [0-9]{10}\.[a-zA-Z0-9]{16}\.[0-9]{0,} */
227 	for (cnt = 0; cnt < 10; ++cnt, ++muid)
228 		if (! isdigit((int)*muid))
229 			return 0;
230 
231 	if (*muid++ != '.')
232 		return 0;
233 
234 	for (cnt = 0; cnt < 16; ++cnt, ++muid)
235 		if (! isalnum((int)*muid))
236 			return 0;
237 
238 	if (*muid++ != '.')
239 		return 0;
240 
241 	for (cnt = 0; *muid != '\0'; ++cnt, ++muid)
242 		if (! isdigit((int)*muid))
243 			return 0;
244 
245 	return (cnt != 0);
246 }
247 
248 char *
249 time_to_text(time_t when)
250 {
251 	struct tm *lt;
252 	static char buf[40];
253 	char *day[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
254 	char *month[] = {"Jan","Feb","Mar","Apr","May","Jun",
255 		       "Jul","Aug","Sep","Oct","Nov","Dec"};
256 
257 	lt = localtime(&when);
258 	if (lt == NULL || when == 0)
259 		fatalx("time_to_text: localtime");
260 
261 	/* We do not use strftime because it is subject to locale substitution*/
262 	if (! bsnprintf(buf, sizeof(buf), "%s, %d %s %d %02d:%02d:%02d %c%02d%02d (%s)",
263 		day[lt->tm_wday], lt->tm_mday, month[lt->tm_mon],
264 		lt->tm_year + 1900,
265 		lt->tm_hour, lt->tm_min, lt->tm_sec,
266 		lt->tm_gmtoff >= 0 ? '+' : '-',
267 		abs((int)lt->tm_gmtoff / 3600),
268 		abs((int)lt->tm_gmtoff % 3600) / 60,
269 		lt->tm_zone))
270 		fatalx("time_to_text: bsnprintf");
271 
272 	return buf;
273 }
274 
275 /*
276  * Check file for security. Based on usr.bin/ssh/auth.c.
277  */
278 int
279 secure_file(int fd, char *path, struct passwd *pw, int mayread)
280 {
281 	char		 buf[MAXPATHLEN];
282 	char		 homedir[MAXPATHLEN];
283 	struct stat	 st;
284 	char		*cp;
285 
286 	if (realpath(path, buf) == NULL)
287 		return 0;
288 
289 	if (realpath(pw->pw_dir, homedir) == NULL)
290 		homedir[0] = '\0';
291 
292 	/* Check the open file to avoid races. */
293 	if (fstat(fd, &st) < 0 ||
294 	    !S_ISREG(st.st_mode) ||
295 	    (st.st_uid != 0 && st.st_uid != pw->pw_uid) ||
296 	    (st.st_mode & (mayread ? 022 : 066)) != 0)
297 		return 0;
298 
299 	/* For each component of the canonical path, walking upwards. */
300 	for (;;) {
301 		if ((cp = dirname(buf)) == NULL)
302 			return 0;
303 		strlcpy(buf, cp, sizeof(buf));
304 
305 		if (stat(buf, &st) < 0 ||
306 		    (st.st_uid != 0 && st.st_uid != pw->pw_uid) ||
307 		    (st.st_mode & 022) != 0)
308 			return 0;
309 
310 		/* We can stop checking after reaching homedir level. */
311 		if (strcmp(homedir, buf) == 0)
312 			break;
313 
314 		/*
315 		 * dirname should always complete with a "/" path,
316 		 * but we can be paranoid and check for "." too
317 		 */
318 		if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0))
319 			break;
320 	}
321 
322 	return 1;
323 }
324 
325 void
326 addargs(arglist *args, char *fmt, ...)
327 {
328 	va_list ap;
329 	char *cp;
330 	u_int nalloc;
331 	int r;
332 
333 	va_start(ap, fmt);
334 	r = vasprintf(&cp, fmt, ap);
335 	va_end(ap);
336 	if (r == -1)
337 		fatal("addargs: argument too long");
338 
339 	nalloc = args->nalloc;
340 	if (args->list == NULL) {
341 		nalloc = 32;
342 		args->num = 0;
343 	} else if (args->num+2 >= nalloc)
344 		nalloc *= 2;
345 
346 	if (SIZE_T_MAX / nalloc < sizeof(char *))
347 		fatalx("addargs: nalloc * size > SIZE_T_MAX");
348 	args->list = realloc(args->list, nalloc * sizeof(char *));
349 	if (args->list == NULL)
350 		fatal("addargs: realloc");
351 	args->nalloc = nalloc;
352 	args->list[args->num++] = cp;
353 	args->list[args->num] = NULL;
354 }
355 
356 void
357 lowercase(char *buf, char *s, size_t len)
358 {
359 	if (len == 0)
360 		fatalx("lowercase: len == 0");
361 
362 	if (strlcpy(buf, s, len) >= len)
363 		fatalx("lowercase: truncation");
364 
365 	while (*buf != '\0') {
366 		*buf = tolower((int)*buf);
367 		buf++;
368 	}
369 }
370 
371 void
372 message_set_errormsg(struct message *messagep, char *fmt, ...)
373 {
374 	int ret;
375 	va_list ap;
376 
377 	va_start(ap, fmt);
378 
379 	ret = vsnprintf(messagep->session_errorline, MAX_LINE_SIZE, fmt, ap);
380 	if (ret >= MAX_LINE_SIZE)
381 		strlcpy(messagep->session_errorline + (MAX_LINE_SIZE - 4), "...", 4);
382 
383 	/* this should not happen */
384 	if (ret == -1)
385 		err(1, "vsnprintf");
386 
387 	va_end(ap);
388 }
389 
390 char *
391 message_get_errormsg(struct message *messagep)
392 {
393 	return messagep->session_errorline;
394 }
395 
396 void
397 sa_set_port(struct sockaddr *sa, int port)
398 {
399 	char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
400 	struct addrinfo hints, *res;
401 	int error;
402 
403 	error = getnameinfo(sa, sa->sa_len, hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST);
404 	if (error)
405 		fatalx("sa_set_port: getnameinfo failed");
406 
407 	memset(&hints, 0, sizeof(hints));
408 	hints.ai_family = PF_UNSPEC;
409 	hints.ai_socktype = SOCK_STREAM;
410 	hints.ai_flags = AI_NUMERICHOST|AI_NUMERICSERV;
411 
412 	snprintf(sbuf, sizeof(sbuf), "%d", port);
413 
414 	error = getaddrinfo(hbuf, sbuf, &hints, &res);
415 	if (error)
416 		fatalx("sa_set_port: getaddrinfo failed");
417 
418 	memcpy(sa, res->ai_addr, res->ai_addrlen);
419 	freeaddrinfo(res);
420 }
421