xref: /openbsd-src/usr.sbin/smtpd/util.c (revision a28daedfc357b214be5c701aa8ba8adb29a7f1c2)
1 /*	$OpenBSD: util.c,v 1.20 2009/04/24 10:02:35 jacekm Exp $	*/
2 
3 /*
4  * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org>
5  * Copyright (c) 2000,2001 Markus Friedl.  All rights reserved.
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 #include <sys/types.h>
21 #include <sys/param.h>
22 #include <sys/queue.h>
23 #include <sys/tree.h>
24 #include <sys/socket.h>
25 #include <sys/stat.h>
26 
27 #include <ctype.h>
28 #include <errno.h>
29 #include <event.h>
30 #include <libgen.h>
31 #include <netdb.h>
32 #include <pwd.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <time.h>
37 #include <unistd.h>
38 
39 #include "smtpd.h"
40 
41 int
42 bsnprintf(char *str, size_t size, const char *format, ...)
43 {
44 	int ret;
45 	va_list ap;
46 
47 	va_start(ap, format);
48 	ret = vsnprintf(str, size, format, ap);
49 	va_end(ap);
50 	if (ret == -1 || ret >= (int)size)
51 		return 0;
52 
53 	return 1;
54 }
55 
56 /* Close file, signifying temporary error condition (if any) to the caller. */
57 int
58 safe_fclose(FILE *fp)
59 {
60 	if (ferror(fp)) {
61 		fclose(fp);
62 		return 0;
63 	}
64 	if (fflush(fp)) {
65 		fclose(fp);
66 		if (errno == ENOSPC)
67 			return 0;
68 		fatal("safe_fclose: fflush");
69 	}
70 	if (fsync(fileno(fp)))
71 		fatal("safe_fclose: fsync");
72 	if (fclose(fp))
73 		fatal("safe_fclose: fclose");
74 
75 	return 1;
76 }
77 
78 struct passwd *
79 safe_getpwnam(const char *name)
80 {
81 	struct passwd *ret;
82 
83 	ret = getpwnam(name);
84 	endpwent();
85 
86 	return ret;
87 }
88 
89 struct passwd *
90 safe_getpwuid(uid_t uid)
91 {
92 	struct passwd *ret;
93 
94 	ret = getpwuid(uid);
95 	endpwent();
96 
97 	return ret;
98 }
99 
100 int
101 hostname_match(char *hostname, char *pattern)
102 {
103 	while (*pattern != '\0' && *hostname != '\0') {
104 		if (*pattern == '*') {
105 			while (*pattern == '*')
106 				pattern++;
107 			while (*hostname != '\0' &&
108 			    tolower(*hostname) != tolower(*pattern))
109 				hostname++;
110 			continue;
111 		}
112 
113 		if (tolower(*pattern) != tolower(*hostname))
114 			return 0;
115 		pattern++;
116 		hostname++;
117 	}
118 
119 	return (*hostname == '\0' && *pattern == '\0');
120 }
121 
122 int
123 recipient_to_path(struct path *path, char *recipient)
124 {
125 	char *username;
126 	char *hostname;
127 
128 	username = recipient;
129 	hostname = strrchr(username, '@');
130 
131 	if (username[0] == '\0') {
132 		*path->user = '\0';
133 		*path->domain = '\0';
134 		return 1;
135 	}
136 
137 	if (hostname == NULL) {
138 		if (strcasecmp(username, "postmaster") != 0)
139 			return 0;
140 		hostname = "localhost";
141 	} else {
142 		*hostname++ = '\0';
143 	}
144 
145 	if (strlcpy(path->user, username, sizeof(path->user))
146 	    >= sizeof(path->user))
147 		return 0;
148 
149 	if (strlcpy(path->domain, hostname, sizeof(path->domain))
150 	    >= sizeof(path->domain))
151 		return 0;
152 
153 	return 1;
154 }
155 
156 int
157 valid_localpart(char *s)
158 {
159 #define IS_ATEXT(c)     (isalnum(c) || strchr("!#$%&'*+-/=?^_`{|}~", (c)))
160 nextatom:
161         if (! IS_ATEXT(*s) || *s == '\0')
162                 return 0;
163         while (*(++s) != '\0') {
164                 if (*s == '.')
165                         break;
166                 if (IS_ATEXT(*s))
167                         continue;
168                 return 0;
169         }
170         if (*s == '.') {
171                 s++;
172                 goto nextatom;
173         }
174         return 1;
175 }
176 
177 int
178 valid_domainpart(char *s)
179 {
180 nextsub:
181         if (!isalnum(*s))
182                 return 0;
183         while (*(++s) != '\0') {
184                 if (*s == '.')
185                         break;
186                 if (isalnum(*s) || *s == '-')
187                         continue;
188                 return 0;
189         }
190         if (s[-1] == '-')
191                 return 0;
192         if (*s == '.') {
193 		s++;
194                 goto nextsub;
195 	}
196         return 1;
197 }
198 
199 char *
200 ss_to_text(struct sockaddr_storage *ss)
201 {
202 	static char	 buf[NI_MAXHOST + 5];
203 	char		*p;
204 
205 	buf[0] = '\0';
206 	p = buf;
207 
208 	if (ss->ss_family == PF_INET6) {
209 		strlcpy(buf, "IPv6:", sizeof(buf));
210 		p = buf + 5;
211 	}
212 
213 	if (getnameinfo((struct sockaddr *)ss, ss->ss_len, p,
214 	    NI_MAXHOST, NULL, 0, NI_NUMERICHOST))
215 		fatalx("ss_to_text: getnameinfo");
216 
217 	return (buf);
218 }
219 
220 int
221 valid_message_id(char *mid)
222 {
223 	u_int8_t cnt;
224 
225 	/* [0-9]{10}\.[a-zA-Z0-9]{16} */
226 	for (cnt = 0; cnt < 10; ++cnt, ++mid)
227 		if (! isdigit((int)*mid))
228 			return 0;
229 
230 	if (*mid++ != '.')
231 		return 0;
232 
233 	for (cnt = 0; cnt < 16; ++cnt, ++mid)
234 		if (! isalnum((int)*mid))
235 			return 0;
236 
237 	return (*mid == '\0');
238 }
239 
240 int
241 valid_message_uid(char *muid)
242 {
243 	u_int8_t cnt;
244 
245 	/* [0-9]{10}\.[a-zA-Z0-9]{16}\.[0-9]{0,} */
246 	for (cnt = 0; cnt < 10; ++cnt, ++muid)
247 		if (! isdigit((int)*muid))
248 			return 0;
249 
250 	if (*muid++ != '.')
251 		return 0;
252 
253 	for (cnt = 0; cnt < 16; ++cnt, ++muid)
254 		if (! isalnum((int)*muid))
255 			return 0;
256 
257 	if (*muid++ != '.')
258 		return 0;
259 
260 	for (cnt = 0; *muid != '\0'; ++cnt, ++muid)
261 		if (! isdigit(*muid))
262 			return 0;
263 
264 	return (cnt != 0);
265 }
266 
267 char *
268 time_to_text(time_t when)
269 {
270 	struct tm *lt;
271 	static char buf[40];
272 	char *day[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
273 	char *month[] = {"Jan","Feb","Mar","Apr","May","Jun",
274 		       "Jul","Aug","Sep","Oct","Nov","Dec"};
275 
276 	lt = localtime(&when);
277 	if (lt == NULL || when == 0)
278 		fatalx("time_to_text: localtime");
279 
280 	/* We do not use strftime because it is subject to locale substitution*/
281 	if (! bsnprintf(buf, sizeof(buf), "%s, %d %s %d %02d:%02d:%02d %c%02d%02d (%s)",
282 		day[lt->tm_wday], lt->tm_mday, month[lt->tm_mon],
283 		lt->tm_year + 1900,
284 		lt->tm_hour, lt->tm_min, lt->tm_sec,
285 		lt->tm_gmtoff >= 0 ? '+' : '-',
286 		abs((int)lt->tm_gmtoff / 3600),
287 		abs((int)lt->tm_gmtoff % 3600) / 60,
288 		lt->tm_zone))
289 		fatalx("time_to_text: bsnprintf");
290 
291 	return buf;
292 }
293 
294 /*
295  * Check file for security. Based on usr.bin/ssh/auth.c.
296  */
297 int
298 secure_file(int fd, char *path, struct passwd *pw)
299 {
300 	char		 buf[MAXPATHLEN];
301 	char		 homedir[MAXPATHLEN];
302 	struct stat	 st;
303 	char		*cp;
304 
305 	if (realpath(path, buf) == NULL)
306 		return 0;
307 
308 	if (realpath(pw->pw_dir, homedir) == NULL)
309 		homedir[0] = '\0';
310 
311 	/* Check the open file to avoid races. */
312 	if (fstat(fd, &st) < 0 ||
313 	    !S_ISREG(st.st_mode) ||
314 	    (st.st_uid != 0 && st.st_uid != pw->pw_uid) ||
315 	    (st.st_mode & 066) != 0)
316 		return 0;
317 
318 	/* For each component of the canonical path, walking upwards. */
319 	for (;;) {
320 		if ((cp = dirname(buf)) == NULL)
321 			return 0;
322 		strlcpy(buf, cp, sizeof(buf));
323 
324 		if (stat(buf, &st) < 0 ||
325 		    (st.st_uid != 0 && st.st_uid != pw->pw_uid) ||
326 		    (st.st_mode & 022) != 0)
327 			return 0;
328 
329 		/* We can stop checking after reaching homedir level. */
330 		if (strcmp(homedir, buf) == 0)
331 			break;
332 
333 		/*
334 		 * dirname should always complete with a "/" path,
335 		 * but we can be paranoid and check for "." too
336 		 */
337 		if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0))
338 			break;
339 	}
340 
341 	return 1;
342 }
343 
344 void
345 addargs(arglist *args, char *fmt, ...)
346 {
347 	va_list ap;
348 	char *cp;
349 	u_int nalloc;
350 	int r;
351 
352 	va_start(ap, fmt);
353 	r = vasprintf(&cp, fmt, ap);
354 	va_end(ap);
355 	if (r == -1)
356 		fatal("addargs: argument too long");
357 
358 	nalloc = args->nalloc;
359 	if (args->list == NULL) {
360 		nalloc = 32;
361 		args->num = 0;
362 	} else if (args->num+2 >= nalloc)
363 		nalloc *= 2;
364 
365 	if (SIZE_T_MAX / nalloc < sizeof(char *))
366 		fatalx("addargs: nalloc * size > SIZE_T_MAX");
367 	args->list = realloc(args->list, nalloc * sizeof(char *));
368 	if (args->list == NULL)
369 		fatal("addargs: realloc");
370 	args->nalloc = nalloc;
371 	args->list[args->num++] = cp;
372 	args->list[args->num] = NULL;
373 }
374 
375 void
376 lowercase(char *buf, char *s, size_t len)
377 {
378 	if (len == 0)
379 		fatalx("lowercase: len == 0");
380 
381 	if (strlcpy(buf, s, len) >= len)
382 		fatalx("lowercase: truncation");
383 
384 	while (*buf != '\0') {
385 		*buf = tolower(*buf);
386 		buf++;
387 	}
388 }
389