1*91ef716eSjsg /* $OpenBSD: util.c,v 1.159 2024/06/02 23:26:39 jsg Exp $ */
2939984b2Sgilles
3939984b2Sgilles /*
430183bdbSjacekm * Copyright (c) 2000,2001 Markus Friedl. All rights reserved.
565c4fdfbSgilles * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org>
6e5b07014Sgilles * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net>
772bc847cSeric * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
8939984b2Sgilles *
9939984b2Sgilles * Permission to use, copy, modify, and distribute this software for any
10939984b2Sgilles * purpose with or without fee is hereby granted, provided that the above
11939984b2Sgilles * copyright notice and this permission notice appear in all copies.
12939984b2Sgilles *
13939984b2Sgilles * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14939984b2Sgilles * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15939984b2Sgilles * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16939984b2Sgilles * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17939984b2Sgilles * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18939984b2Sgilles * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19939984b2Sgilles * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20939984b2Sgilles */
21939984b2Sgilles
2244307885Sjacekm #include <sys/stat.h>
23939984b2Sgilles
2425080696Sgilles #include <netinet/in.h>
2525080696Sgilles
26d3140113Seric #include <arpa/inet.h>
272b90997cSgilles #include <ctype.h>
28939984b2Sgilles #include <errno.h>
29591aa05bSeric #include <fts.h>
3044307885Sjacekm #include <libgen.h>
31254aed36Seric #include <resolv.h>
32378b56faSgilles #include <stdarg.h>
3344307885Sjacekm #include <stdlib.h>
34939984b2Sgilles #include <string.h>
35ccfb4053Seric #include <syslog.h>
36939984b2Sgilles #include <unistd.h>
37939984b2Sgilles
38939984b2Sgilles #include "smtpd.h"
395eb8dddaSgilles #include "log.h"
40939984b2Sgilles
4136e884f4Ssunil static int parse_mailname_file(char *, size_t);
4225080696Sgilles
43f24248b7Sreyk int tracing = 0;
44f24248b7Sreyk int foreground_log = 0;
45f24248b7Sreyk
4672bc847cSeric void *
xmalloc(size_t size)47118c16f3Sgilles xmalloc(size_t size)
4872bc847cSeric {
4972bc847cSeric void *r;
5072bc847cSeric
51118c16f3Sgilles if ((r = malloc(size)) == NULL)
52118c16f3Sgilles fatal("malloc");
5372bc847cSeric
5472bc847cSeric return (r);
5572bc847cSeric }
5672bc847cSeric
5772bc847cSeric void *
xcalloc(size_t nmemb,size_t size)58118c16f3Sgilles xcalloc(size_t nmemb, size_t size)
5972bc847cSeric {
6072bc847cSeric void *r;
6172bc847cSeric
62118c16f3Sgilles if ((r = calloc(nmemb, size)) == NULL)
63118c16f3Sgilles fatal("calloc");
6472bc847cSeric
6572bc847cSeric return (r);
6672bc847cSeric }
6772bc847cSeric
6872bc847cSeric char *
xstrdup(const char * str)69118c16f3Sgilles xstrdup(const char *str)
7072bc847cSeric {
7172bc847cSeric char *r;
7272bc847cSeric
73118c16f3Sgilles if ((r = strdup(str)) == NULL)
74118c16f3Sgilles fatal("strdup");
7572bc847cSeric
7672bc847cSeric return (r);
7772bc847cSeric }
7872bc847cSeric
79591e1804Seric void *
xmemdup(const void * ptr,size_t size)80118c16f3Sgilles xmemdup(const void *ptr, size_t size)
81591e1804Seric {
82591e1804Seric void *r;
83591e1804Seric
84118c16f3Sgilles if ((r = malloc(size)) == NULL)
85118c16f3Sgilles fatal("malloc");
86118c16f3Sgilles
87591e1804Seric memmove(r, ptr, size);
88591e1804Seric
89591e1804Seric return (r);
90591e1804Seric }
91591e1804Seric
92118c16f3Sgilles int
xasprintf(char ** ret,const char * format,...)93118c16f3Sgilles xasprintf(char **ret, const char *format, ...)
942df8b630Sgilles {
95118c16f3Sgilles int r;
962df8b630Sgilles va_list ap;
972df8b630Sgilles
982df8b630Sgilles va_start(ap, format);
99118c16f3Sgilles r = vasprintf(ret, format, ap);
1002df8b630Sgilles va_end(ap);
101118c16f3Sgilles if (r == -1)
102118c16f3Sgilles fatal("vasprintf");
103118c16f3Sgilles
104118c16f3Sgilles return (r);
1052df8b630Sgilles }
1062df8b630Sgilles
1072df8b630Sgilles
10893f98431Schl #if !defined(NO_IO)
10966802da1Seric int
io_xprintf(struct io * io,const char * fmt,...)11066802da1Seric io_xprintf(struct io *io, const char *fmt, ...)
11166802da1Seric {
11266802da1Seric va_list ap;
11366802da1Seric int len;
11466802da1Seric
11566802da1Seric va_start(ap, fmt);
11666802da1Seric len = io_vprintf(io, fmt, ap);
11766802da1Seric va_end(ap);
11866802da1Seric if (len == -1)
11966802da1Seric fatal("io_xprintf(%p, %s, ...)", io, fmt);
12066802da1Seric
12166802da1Seric return len;
12266802da1Seric }
12366802da1Seric
12466802da1Seric int
io_xprint(struct io * io,const char * str)12566802da1Seric io_xprint(struct io *io, const char *str)
12666802da1Seric {
12766802da1Seric int len;
12866802da1Seric
12966802da1Seric len = io_print(io, str);
13066802da1Seric if (len == -1)
13166802da1Seric fatal("io_xprint(%p, %s, ...)", io, str);
13266802da1Seric
13366802da1Seric return len;
13466802da1Seric }
13593f98431Schl #endif
13693f98431Schl
13765c4fdfbSgilles char *
strip(char * s)13865c4fdfbSgilles strip(char *s)
13965c4fdfbSgilles {
14065c4fdfbSgilles size_t l;
14165c4fdfbSgilles
142b2232dc0Sgilles while (isspace((unsigned char)*s))
14365c4fdfbSgilles s++;
14465c4fdfbSgilles
14565c4fdfbSgilles for (l = strlen(s); l; l--) {
146b2232dc0Sgilles if (!isspace((unsigned char)s[l-1]))
14765c4fdfbSgilles break;
14865c4fdfbSgilles s[l-1] = '\0';
14965c4fdfbSgilles }
15065c4fdfbSgilles
15165c4fdfbSgilles return (s);
15265c4fdfbSgilles }
15365c4fdfbSgilles
154939984b2Sgilles int
bsnprintf(char * str,size_t size,const char * format,...)155939984b2Sgilles bsnprintf(char *str, size_t size, const char *format, ...)
156939984b2Sgilles {
157939984b2Sgilles int ret;
158939984b2Sgilles va_list ap;
159939984b2Sgilles
160939984b2Sgilles va_start(ap, format);
161939984b2Sgilles ret = vsnprintf(str, size, format, ap);
162939984b2Sgilles va_end(ap);
16396051a29Stb if (ret < 0 || (size_t)ret >= size)
164939984b2Sgilles return 0;
165939984b2Sgilles
166939984b2Sgilles return 1;
167939984b2Sgilles }
168a933d224Sjacekm
169fc240c15Sgilles
1707a71916fSeric int
ckdir(const char * path,mode_t mode,uid_t owner,gid_t group,int create)1717a71916fSeric ckdir(const char *path, mode_t mode, uid_t owner, gid_t group, int create)
1727a71916fSeric {
1737a71916fSeric char mode_str[12];
1747a71916fSeric int ret;
1757a71916fSeric struct stat sb;
1767a71916fSeric
1777a71916fSeric if (stat(path, &sb) == -1) {
1787a71916fSeric if (errno != ENOENT || create == 0) {
179b832907bSeric log_warn("stat: %s", path);
1807a71916fSeric return (0);
1817a71916fSeric }
1827a71916fSeric
1837a71916fSeric /* chmod is deferred to avoid umask effect */
1847a71916fSeric if (mkdir(path, 0) == -1) {
185b832907bSeric log_warn("mkdir: %s", path);
1867a71916fSeric return (0);
1877a71916fSeric }
1887a71916fSeric
1897a71916fSeric if (chown(path, owner, group) == -1) {
190b832907bSeric log_warn("chown: %s", path);
1917a71916fSeric return (0);
1927a71916fSeric }
1937a71916fSeric
1947a71916fSeric if (chmod(path, mode) == -1) {
195b832907bSeric log_warn("chmod: %s", path);
1967a71916fSeric return (0);
1977a71916fSeric }
1987a71916fSeric
1997a71916fSeric if (stat(path, &sb) == -1) {
200b832907bSeric log_warn("stat: %s", path);
2017a71916fSeric return (0);
2027a71916fSeric }
2037a71916fSeric }
2047a71916fSeric
2057a71916fSeric ret = 1;
2067a71916fSeric
2077a71916fSeric /* check if it's a directory */
2087a71916fSeric if (!S_ISDIR(sb.st_mode)) {
2097a71916fSeric ret = 0;
210b832907bSeric log_warnx("%s is not a directory", path);
2117a71916fSeric }
2127a71916fSeric
2137a71916fSeric /* check that it is owned by owner/group */
2147a71916fSeric if (sb.st_uid != owner) {
2157a71916fSeric ret = 0;
216b832907bSeric log_warnx("%s is not owned by uid %d", path, owner);
2177a71916fSeric }
2187a71916fSeric if (sb.st_gid != group) {
2197a71916fSeric ret = 0;
220b832907bSeric log_warnx("%s is not owned by gid %d", path, group);
2217a71916fSeric }
2227a71916fSeric
2237a71916fSeric /* check permission */
2247a71916fSeric if ((sb.st_mode & 07777) != mode) {
2257a71916fSeric ret = 0;
2267a71916fSeric strmode(mode, mode_str);
2277a71916fSeric mode_str[10] = '\0';
228b832907bSeric log_warnx("%s must be %s (%o)", path, mode_str + 1, mode);
2297a71916fSeric }
2307a71916fSeric
2317a71916fSeric return ret;
2327a71916fSeric }
2337a71916fSeric
234591aa05bSeric int
rmtree(char * path,int keepdir)235591aa05bSeric rmtree(char *path, int keepdir)
236591aa05bSeric {
237591aa05bSeric char *path_argv[2];
238591aa05bSeric FTS *fts;
239591aa05bSeric FTSENT *e;
240591aa05bSeric int ret, depth;
241591aa05bSeric
242591aa05bSeric path_argv[0] = path;
243591aa05bSeric path_argv[1] = NULL;
244591aa05bSeric ret = 0;
24565c4fdfbSgilles depth = 0;
246591aa05bSeric
24765c4fdfbSgilles fts = fts_open(path_argv, FTS_PHYSICAL | FTS_NOCHDIR, NULL);
24865c4fdfbSgilles if (fts == NULL) {
249b832907bSeric log_warn("fts_open: %s", path);
250591aa05bSeric return (-1);
251591aa05bSeric }
252591aa05bSeric
253591aa05bSeric while ((e = fts_read(fts)) != NULL) {
25465c4fdfbSgilles switch (e->fts_info) {
25565c4fdfbSgilles case FTS_D:
25665c4fdfbSgilles depth++;
25765c4fdfbSgilles break;
25865c4fdfbSgilles case FTS_DP:
25965c4fdfbSgilles case FTS_DNR:
260591aa05bSeric depth--;
26165c4fdfbSgilles if (keepdir && depth == 0)
262591aa05bSeric continue;
263591aa05bSeric if (rmdir(e->fts_path) == -1) {
264b832907bSeric log_warn("rmdir: %s", e->fts_path);
265591aa05bSeric ret = -1;
266591aa05bSeric }
26765c4fdfbSgilles break;
268591aa05bSeric
26965c4fdfbSgilles case FTS_F:
270591aa05bSeric if (unlink(e->fts_path) == -1) {
271b832907bSeric log_warn("unlink: %s", e->fts_path);
272591aa05bSeric ret = -1;
273591aa05bSeric }
274591aa05bSeric }
27565c4fdfbSgilles }
276591aa05bSeric
277591aa05bSeric fts_close(fts);
278591aa05bSeric
279591aa05bSeric return (ret);
280591aa05bSeric }
281591aa05bSeric
282591aa05bSeric int
mvpurge(char * from,char * to)283591aa05bSeric mvpurge(char *from, char *to)
284591aa05bSeric {
285591aa05bSeric size_t n;
286591aa05bSeric int retry;
287591aa05bSeric const char *sep;
288953aae25Sderaadt char buf[PATH_MAX];
289591aa05bSeric
290591aa05bSeric if ((n = strlen(to)) == 0)
291591aa05bSeric fatalx("to is empty");
292591aa05bSeric
293591aa05bSeric sep = (to[n - 1] == '/') ? "" : "/";
294591aa05bSeric retry = 0;
295591aa05bSeric
296591aa05bSeric again:
2979b886139Sgilles (void)snprintf(buf, sizeof buf, "%s%s%u", to, sep, arc4random());
298591aa05bSeric if (rename(from, buf) == -1) {
299591aa05bSeric /* ENOTDIR has actually 2 meanings, and incorrect input
300591aa05bSeric * could lead to an infinite loop. Consider that after
301591aa05bSeric * 20 tries something is hopelessly wrong.
302591aa05bSeric */
303591aa05bSeric if (errno == ENOTEMPTY || errno == EISDIR || errno == ENOTDIR) {
304591aa05bSeric if ((retry++) >= 20)
305591aa05bSeric return (-1);
306591aa05bSeric goto again;
307591aa05bSeric }
308591aa05bSeric return -1;
309591aa05bSeric }
310591aa05bSeric
311591aa05bSeric return 0;
312591aa05bSeric }
313591aa05bSeric
3147a71916fSeric
3150e8cc8ecSchl int
mktmpfile(void)3160e8cc8ecSchl mktmpfile(void)
3170e8cc8ecSchl {
318953aae25Sderaadt char path[PATH_MAX];
3190e8cc8ecSchl int fd;
3200e8cc8ecSchl
3217bdbba2fSeric if (!bsnprintf(path, sizeof(path), "%s/smtpd.XXXXXXXXXX",
322b832907bSeric PATH_TEMPORARY)) {
323b832907bSeric log_warn("snprintf");
324b832907bSeric fatal("exiting");
325b832907bSeric }
3260e8cc8ecSchl
327b832907bSeric if ((fd = mkstemp(path)) == -1) {
328b832907bSeric log_warn("cannot create temporary file %s", path);
329b832907bSeric fatal("exiting");
330b832907bSeric }
3310e8cc8ecSchl unlink(path);
3320e8cc8ecSchl return (fd);
3330e8cc8ecSchl }
3340e8cc8ecSchl
3350e8cc8ecSchl
336e5b07014Sgilles /* Close file, signifying temporary error condition (if any) to the caller. */
337e5b07014Sgilles int
safe_fclose(FILE * fp)338e5b07014Sgilles safe_fclose(FILE *fp)
339e5b07014Sgilles {
340e5b07014Sgilles if (ferror(fp)) {
341e5b07014Sgilles fclose(fp);
342e5b07014Sgilles return 0;
343e5b07014Sgilles }
344e5b07014Sgilles if (fflush(fp)) {
345e5b07014Sgilles fclose(fp);
346e5b07014Sgilles if (errno == ENOSPC)
347e5b07014Sgilles return 0;
348e5b07014Sgilles fatal("safe_fclose: fflush");
349e5b07014Sgilles }
350e5b07014Sgilles if (fsync(fileno(fp)))
351e5b07014Sgilles fatal("safe_fclose: fsync");
352e5b07014Sgilles if (fclose(fp))
353e5b07014Sgilles fatal("safe_fclose: fclose");
354e5b07014Sgilles
355e5b07014Sgilles return 1;
356e5b07014Sgilles }
357e5b07014Sgilles
3582b90997cSgilles int
hostname_match(const char * hostname,const char * pattern)3597791da2bSeric hostname_match(const char *hostname, const char *pattern)
3602b90997cSgilles {
3612b90997cSgilles while (*pattern != '\0' && *hostname != '\0') {
3622b90997cSgilles if (*pattern == '*') {
3632b90997cSgilles while (*pattern == '*')
3642b90997cSgilles pattern++;
3652b90997cSgilles while (*hostname != '\0' &&
366fc3a8311Seric tolower((unsigned char)*hostname) !=
367fc3a8311Seric tolower((unsigned char)*pattern))
3682b90997cSgilles hostname++;
3692b90997cSgilles continue;
3702b90997cSgilles }
3712b90997cSgilles
372fc3a8311Seric if (tolower((unsigned char)*pattern) !=
373fc3a8311Seric tolower((unsigned char)*hostname))
3742b90997cSgilles return 0;
3752b90997cSgilles pattern++;
3762b90997cSgilles hostname++;
3772b90997cSgilles }
3782b90997cSgilles
3792b90997cSgilles return (*hostname == '\0' && *pattern == '\0');
3802b90997cSgilles }
38177a2e6faSgilles
38277a2e6faSgilles int
mailaddr_match(const struct mailaddr * maddr1,const struct mailaddr * maddr2)3832b4f6ebbSgilles mailaddr_match(const struct mailaddr *maddr1, const struct mailaddr *maddr2)
3842b4f6ebbSgilles {
3852b4f6ebbSgilles struct mailaddr m1 = *maddr1;
3862b4f6ebbSgilles struct mailaddr m2 = *maddr2;
3872b4f6ebbSgilles char *p;
3882b4f6ebbSgilles
3892b4f6ebbSgilles /* catchall */
3902b4f6ebbSgilles if (m2.user[0] == '\0' && m2.domain[0] == '\0')
3912b4f6ebbSgilles return 1;
3922b4f6ebbSgilles
39369479998Sgilles if (m2.domain[0] && !hostname_match(m1.domain, m2.domain))
3942b4f6ebbSgilles return 0;
3952b4f6ebbSgilles
3962b4f6ebbSgilles if (m2.user[0]) {
3972b4f6ebbSgilles /* if address from table has a tag, we must respect it */
39837e5d029Sgilles if (strchr(m2.user, *env->sc_subaddressing_delim) == NULL) {
3992b4f6ebbSgilles /* otherwise, strip tag from session address if any */
40037e5d029Sgilles p = strchr(m1.user, *env->sc_subaddressing_delim);
4012b4f6ebbSgilles if (p)
4022b4f6ebbSgilles *p = '\0';
4032b4f6ebbSgilles }
4042b4f6ebbSgilles if (strcasecmp(m1.user, m2.user))
4052b4f6ebbSgilles return 0;
4062b4f6ebbSgilles }
4072b4f6ebbSgilles return 1;
4082b4f6ebbSgilles }
4092b4f6ebbSgilles
4102b4f6ebbSgilles int
valid_localpart(const char * s)41122507732Schl valid_localpart(const char *s)
41271442faaSjacekm {
413b15221a5Seric #define IS_ATEXT(c) (isalnum((unsigned char)(c)) || strchr(MAILADDR_ALLOWED, (c)))
41471442faaSjacekm nextatom:
41571442faaSjacekm if (!IS_ATEXT(*s) || *s == '\0')
41671442faaSjacekm return 0;
41771442faaSjacekm while (*(++s) != '\0') {
41871442faaSjacekm if (*s == '.')
41971442faaSjacekm break;
42071442faaSjacekm if (IS_ATEXT(*s))
42171442faaSjacekm continue;
42271442faaSjacekm return 0;
42371442faaSjacekm }
42471442faaSjacekm if (*s == '.') {
42571442faaSjacekm s++;
42671442faaSjacekm goto nextatom;
42771442faaSjacekm }
42871442faaSjacekm return 1;
42971442faaSjacekm }
43071442faaSjacekm
43171442faaSjacekm int
valid_domainpart(const char * s)43222507732Schl valid_domainpart(const char *s)
43371442faaSjacekm {
43422507732Schl struct in_addr ina;
43522507732Schl struct in6_addr ina6;
436299c4efeSeric char *c, domain[SMTPD_MAXDOMAINPARTSIZE];
437299c4efeSeric const char *p;
4383d2f1f8eSeric size_t dlen;
43922507732Schl
44022507732Schl if (*s == '[') {
441299c4efeSeric if (strncasecmp("[IPv6:", s, 6) == 0)
442299c4efeSeric p = s + 6;
443299c4efeSeric else
444299c4efeSeric p = s + 1;
445299c4efeSeric
446299c4efeSeric if (strlcpy(domain, p, sizeof domain) >= sizeof domain)
447299c4efeSeric return 0;
44822507732Schl
449000eaaf0Smillert c = strchr(domain, ']');
45022507732Schl if (!c || c[1] != '\0')
45122507732Schl return 0;
45222507732Schl
45322507732Schl *c = '\0';
45422507732Schl
45522507732Schl if (inet_pton(AF_INET6, domain, &ina6) == 1)
45622507732Schl return 1;
45722507732Schl if (inet_pton(AF_INET, domain, &ina) == 1)
45822507732Schl return 1;
45922507732Schl
46022507732Schl return 0;
46122507732Schl }
46222507732Schl
4633d2f1f8eSeric if (*s == '\0')
4643d2f1f8eSeric return 0;
4653d2f1f8eSeric
4663d2f1f8eSeric dlen = strlen(s);
4673d2f1f8eSeric if (dlen >= sizeof domain)
4683d2f1f8eSeric return 0;
4693d2f1f8eSeric
4703d2f1f8eSeric if (s[dlen - 1] == '.')
4713d2f1f8eSeric return 0;
4723d2f1f8eSeric
4733d2f1f8eSeric return res_hnok(s);
4740264247fSeric }
4750264247fSeric
476000eaaf0Smillert #define LABELCHR(c) ((c) == '-' || (c) == '_' || isalpha((unsigned char)(c)) || isdigit((unsigned char)(c)))
4779ac382e5Seric #define LABELMAX 63
4789ac382e5Seric #define DNAMEMAX 253
4790264247fSeric
4800264247fSeric int
valid_domainname(const char * str)4819ac382e5Seric valid_domainname(const char *str)
4820264247fSeric {
4839ac382e5Seric const char *label, *s;
4840264247fSeric
4859ac382e5Seric /*
4869ac382e5Seric * Expect a sequence of dot-separated labels, possibly with a trailing
4879ac382e5Seric * dot. The empty string is rejected, as well a single dot.
4889ac382e5Seric */
4899ac382e5Seric for (s = str; *s; s++) {
4900264247fSeric
4919ac382e5Seric /* Start of a new label. */
4920264247fSeric label = s;
4939ac382e5Seric while (LABELCHR(*s))
4940264247fSeric s++;
4950264247fSeric
4969ac382e5Seric /* Must have at least one char and at most LABELMAX. */
4979ac382e5Seric if (s == label || s - label > LABELMAX)
4985f2c0e3cSgilles return 0;
4995f2c0e3cSgilles
5009ac382e5Seric /* If last label, stop here. */
5019ac382e5Seric if (*s == '\0')
5029ac382e5Seric break;
5039ac382e5Seric
5049ac382e5Seric /* Expect a dot as label separator or last char. */
5050264247fSeric if (*s != '.')
506fad1310eSgilles return 0;
5070264247fSeric }
5089ac382e5Seric
5099ac382e5Seric /* Must have at leat one label and no more than DNAMEMAX chars. */
5109ac382e5Seric if (s == str || s - str > DNAMEMAX)
5119ac382e5Seric return 0;
5129ac382e5Seric
5139ac382e5Seric return 1;
51471442faaSjacekm }
5152697a4d9Sjacekm
51644307885Sjacekm int
valid_smtp_response(const char * s)5177ebec07bSgilles valid_smtp_response(const char *s)
5187ebec07bSgilles {
5197ebec07bSgilles if (strlen(s) < 5)
5207ebec07bSgilles return 0;
5217ebec07bSgilles
5227ebec07bSgilles if ((s[0] < '2' || s[0] > '5') ||
5237ebec07bSgilles (s[1] < '0' || s[1] > '9') ||
5247ebec07bSgilles (s[2] < '0' || s[2] > '9') ||
5257ebec07bSgilles (s[3] != ' '))
5267ebec07bSgilles return 0;
5277ebec07bSgilles
5287ebec07bSgilles return 1;
5297ebec07bSgilles }
5307ebec07bSgilles
5317ebec07bSgilles int
valid_xtext(const char * s)532cd8603dbSop valid_xtext(const char *s)
533cd8603dbSop {
534cd8603dbSop for (; *s != '\0'; ++s) {
535cd8603dbSop if (*s < '!' || *s > '~' || *s == '=')
536cd8603dbSop return 0;
537cd8603dbSop
538cd8603dbSop if (*s != '+')
539cd8603dbSop continue;
540cd8603dbSop
541cd8603dbSop s++;
542cd8603dbSop if (!isdigit((unsigned char)*s) &&
543cd8603dbSop !(*s >= 'A' && *s <= 'F'))
544cd8603dbSop return 0;
545cd8603dbSop
546cd8603dbSop s++;
547cd8603dbSop if (!isdigit((unsigned char)*s) &&
548cd8603dbSop !(*s >= 'A' && *s <= 'F'))
549cd8603dbSop return 0;
550cd8603dbSop }
551cd8603dbSop
552cd8603dbSop return 1;
553cd8603dbSop }
554cd8603dbSop
555cd8603dbSop int
secure_file(int fd,char * path,char * userdir,uid_t uid,int mayread)5562ddfddf2Sgilles secure_file(int fd, char *path, char *userdir, uid_t uid, int mayread)
55744307885Sjacekm {
558299c4efeSeric char buf[PATH_MAX];
559299c4efeSeric char homedir[PATH_MAX];
56044307885Sjacekm struct stat st;
56144307885Sjacekm char *cp;
56244307885Sjacekm
56344307885Sjacekm if (realpath(path, buf) == NULL)
56444307885Sjacekm return 0;
56544307885Sjacekm
5662ddfddf2Sgilles if (realpath(userdir, homedir) == NULL)
56744307885Sjacekm homedir[0] = '\0';
56844307885Sjacekm
56944307885Sjacekm /* Check the open file to avoid races. */
570df69c215Sderaadt if (fstat(fd, &st) == -1 ||
57144307885Sjacekm !S_ISREG(st.st_mode) ||
5720fe61fc9Sgilles st.st_uid != uid ||
573d0e4aaa3Sjacekm (st.st_mode & (mayread ? 022 : 066)) != 0)
57444307885Sjacekm return 0;
57544307885Sjacekm
57644307885Sjacekm /* For each component of the canonical path, walking upwards. */
57744307885Sjacekm for (;;) {
57844307885Sjacekm if ((cp = dirname(buf)) == NULL)
57944307885Sjacekm return 0;
5809b886139Sgilles (void)strlcpy(buf, cp, sizeof(buf));
58144307885Sjacekm
582df69c215Sderaadt if (stat(buf, &st) == -1 ||
5832ddfddf2Sgilles (st.st_uid != 0 && st.st_uid != uid) ||
58444307885Sjacekm (st.st_mode & 022) != 0)
58544307885Sjacekm return 0;
58644307885Sjacekm
58744307885Sjacekm /* We can stop checking after reaching homedir level. */
58844307885Sjacekm if (strcmp(homedir, buf) == 0)
58944307885Sjacekm break;
59044307885Sjacekm
59144307885Sjacekm /*
59244307885Sjacekm * dirname should always complete with a "/" path,
59344307885Sjacekm * but we can be paranoid and check for "." too
59444307885Sjacekm */
59544307885Sjacekm if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0))
59644307885Sjacekm break;
59744307885Sjacekm }
59844307885Sjacekm
59944307885Sjacekm return 1;
60044307885Sjacekm }
601f1e656c3Sjacekm
602f1e656c3Sjacekm void
addargs(arglist * args,char * fmt,...)603f1e656c3Sjacekm addargs(arglist *args, char *fmt, ...)
604f1e656c3Sjacekm {
605f1e656c3Sjacekm va_list ap;
606f1e656c3Sjacekm char *cp;
607d2241734Schl uint nalloc;
608f1e656c3Sjacekm int r;
609483e8451Sgilles char **tmp;
610f1e656c3Sjacekm
611f1e656c3Sjacekm va_start(ap, fmt);
612f1e656c3Sjacekm r = vasprintf(&cp, fmt, ap);
613f1e656c3Sjacekm va_end(ap);
614f1e656c3Sjacekm if (r == -1)
615f1e656c3Sjacekm fatal("addargs: argument too long");
616f1e656c3Sjacekm
617f1e656c3Sjacekm nalloc = args->nalloc;
618f1e656c3Sjacekm if (args->list == NULL) {
619f1e656c3Sjacekm nalloc = 32;
620f1e656c3Sjacekm args->num = 0;
621f1e656c3Sjacekm } else if (args->num+2 >= nalloc)
622f1e656c3Sjacekm nalloc *= 2;
623f1e656c3Sjacekm
624483e8451Sgilles tmp = reallocarray(args->list, nalloc, sizeof(char *));
625483e8451Sgilles if (tmp == NULL)
6265ce25fe6Sespie fatal("addargs: reallocarray");
627483e8451Sgilles args->list = tmp;
628f1e656c3Sjacekm args->nalloc = nalloc;
629f1e656c3Sjacekm args->list[args->num++] = cp;
630f1e656c3Sjacekm args->list[args->num] = NULL;
631f1e656c3Sjacekm }
63222d2befeSjacekm
633aac96740Sgilles int
lowercase(char * buf,const char * s,size_t len)634abf71fdfSeric lowercase(char *buf, const char *s, size_t len)
63522d2befeSjacekm {
63622d2befeSjacekm if (len == 0)
637aac96740Sgilles return 0;
63822d2befeSjacekm
63922d2befeSjacekm if (strlcpy(buf, s, len) >= len)
640aac96740Sgilles return 0;
64122d2befeSjacekm
64222d2befeSjacekm while (*buf != '\0') {
643fc3a8311Seric *buf = tolower((unsigned char)*buf);
64422d2befeSjacekm buf++;
64522d2befeSjacekm }
646aac96740Sgilles
647aac96740Sgilles return 1;
648aac96740Sgilles }
649aac96740Sgilles
650ce4786e4Seric int
uppercase(char * buf,const char * s,size_t len)651ce4786e4Seric uppercase(char *buf, const char *s, size_t len)
652ce4786e4Seric {
653ce4786e4Seric if (len == 0)
654ce4786e4Seric return 0;
655ce4786e4Seric
656ce4786e4Seric if (strlcpy(buf, s, len) >= len)
657ce4786e4Seric return 0;
658ce4786e4Seric
659ce4786e4Seric while (*buf != '\0') {
660fc3a8311Seric *buf = toupper((unsigned char)*buf);
661ce4786e4Seric buf++;
662ce4786e4Seric }
663ce4786e4Seric
664ce4786e4Seric return 1;
665ce4786e4Seric }
666ce4786e4Seric
667aac96740Sgilles void
xlowercase(char * buf,const char * s,size_t len)668abf71fdfSeric xlowercase(char *buf, const char *s, size_t len)
669aac96740Sgilles {
670aac96740Sgilles if (len == 0)
671aac96740Sgilles fatalx("lowercase: len == 0");
672aac96740Sgilles
673aac96740Sgilles if (!lowercase(buf, s, len))
674aac96740Sgilles fatalx("lowercase: truncation");
67522d2befeSjacekm }
676378b56faSgilles
677d2241734Schl uint64_t
generate_uid(void)678e8664bd6Sgilles generate_uid(void)
679e8664bd6Sgilles {
68026897f9dSeric static uint32_t id;
681081a0b33Seric static uint8_t inited;
68226897f9dSeric uint64_t uid;
683e8664bd6Sgilles
684081a0b33Seric if (!inited) {
685081a0b33Seric id = arc4random();
686081a0b33Seric inited = 1;
687081a0b33Seric }
68826897f9dSeric while ((uid = ((uint64_t)(id++) << 32 | arc4random())) == 0)
68926897f9dSeric ;
69026897f9dSeric
69126897f9dSeric return (uid);
692e8664bd6Sgilles }
693cb5c228eSjacekm
694f4ef9244Sjacekm int
session_socket_error(int fd)695f4ef9244Sjacekm session_socket_error(int fd)
696f4ef9244Sjacekm {
697d10e47d4Sgilles int error;
698d10e47d4Sgilles socklen_t len;
699f4ef9244Sjacekm
700f4ef9244Sjacekm len = sizeof(error);
701f4ef9244Sjacekm if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) == -1)
702f4ef9244Sjacekm fatal("session_socket_error: getsockopt");
703f4ef9244Sjacekm
704f4ef9244Sjacekm return (error);
705f4ef9244Sjacekm }
70625080696Sgilles
70725080696Sgilles const char *
parse_smtp_response(char * line,size_t len,char ** msg,int * cont)7088a99bc94Seric parse_smtp_response(char *line, size_t len, char **msg, int *cont)
7098a99bc94Seric {
710953aae25Sderaadt if (len >= LINE_MAX)
7118a99bc94Seric return "line too long";
7128a99bc94Seric
7138a99bc94Seric if (len > 3) {
7148a99bc94Seric if (msg)
7158a99bc94Seric *msg = line + 4;
7168a99bc94Seric if (cont)
7178a99bc94Seric *cont = (line[3] == '-');
7188a99bc94Seric } else if (len == 3) {
7198a99bc94Seric if (msg)
7208a99bc94Seric *msg = line + 3;
7218a99bc94Seric if (cont)
7228a99bc94Seric *cont = 0;
7238a99bc94Seric } else
7248a99bc94Seric return "line too short";
7258a99bc94Seric
7268a99bc94Seric /* validate reply code */
727b70263c5Sderaadt if (line[0] < '2' || line[0] > '5' || !isdigit((unsigned char)line[1]) ||
728b70263c5Sderaadt !isdigit((unsigned char)line[2]))
7298a99bc94Seric return "reply code out of range";
7308a99bc94Seric
7318a99bc94Seric return NULL;
7328a99bc94Seric }
733f70d44e6Seric
73436e884f4Ssunil static int
parse_mailname_file(char * hostname,size_t len)73536e884f4Ssunil parse_mailname_file(char *hostname, size_t len)
736f70d44e6Seric {
737f70d44e6Seric FILE *fp;
7382b86a358Sjung char *buf = NULL;
7392b86a358Sjung size_t bufsz = 0;
7402b86a358Sjung ssize_t buflen;
741f70d44e6Seric
7428b79b54eSeric if ((fp = fopen(MAILNAME_FILE, "r")) == NULL)
74336e884f4Ssunil return 1;
744f70d44e6Seric
7458e996a8eSjsg buflen = getline(&buf, &bufsz, fp);
7468e996a8eSjsg fclose(fp);
7478e996a8eSjsg if (buflen == -1) {
7488e996a8eSjsg free(buf);
7498e996a8eSjsg return 1;
7508e996a8eSjsg }
751f70d44e6Seric
752f70d44e6Seric if (buf[buflen - 1] == '\n')
753f70d44e6Seric buf[buflen - 1] = '\0';
754f70d44e6Seric
7558e996a8eSjsg bufsz = strlcpy(hostname, buf, len);
7568e996a8eSjsg free(buf);
7578e996a8eSjsg if (bufsz >= len) {
7588b79b54eSeric fprintf(stderr, MAILNAME_FILE " entry too long");
7598e996a8eSjsg return 1;
760f70d44e6Seric }
761f70d44e6Seric
76236e884f4Ssunil return 0;
76336e884f4Ssunil }
76436e884f4Ssunil
76536e884f4Ssunil int
getmailname(char * hostname,size_t len)76636e884f4Ssunil getmailname(char *hostname, size_t len)
76736e884f4Ssunil {
76836e884f4Ssunil struct addrinfo hints, *res = NULL;
76936e884f4Ssunil int error;
77036e884f4Ssunil
77136e884f4Ssunil /* Try MAILNAME_FILE first */
77236e884f4Ssunil if (parse_mailname_file(hostname, len) == 0)
77336e884f4Ssunil return 0;
77436e884f4Ssunil
77536e884f4Ssunil /* Next, gethostname(3) */
776f70d44e6Seric if (gethostname(hostname, len) == -1) {
77736e884f4Ssunil fprintf(stderr, "getmailname: gethostname() failed\n");
77836e884f4Ssunil return -1;
779f70d44e6Seric }
780f70d44e6Seric
78136e884f4Ssunil if (strchr(hostname, '.') != NULL)
78236e884f4Ssunil return 0;
78336e884f4Ssunil
78436e884f4Ssunil /* Canonicalize if domain part is missing */
785f70d44e6Seric memset(&hints, 0, sizeof hints);
786f70d44e6Seric hints.ai_family = PF_UNSPEC;
787f70d44e6Seric hints.ai_flags = AI_CANONNAME;
788f70d44e6Seric error = getaddrinfo(hostname, NULL, &hints, &res);
78936e884f4Ssunil if (error)
79036e884f4Ssunil return 0; /* Continue with non-canon hostname */
791f70d44e6Seric
792f70d44e6Seric if (strlcpy(hostname, res->ai_canonname, len) >= len) {
793f70d44e6Seric fprintf(stderr, "hostname too long");
794272e865cSgilles freeaddrinfo(res);
79536e884f4Ssunil return -1;
796f70d44e6Seric }
797f70d44e6Seric
798f70d44e6Seric freeaddrinfo(res);
79936e884f4Ssunil return 0;
800f70d44e6Seric }
801254aed36Seric
802254aed36Seric int
base64_encode(unsigned char const * src,size_t srclen,char * dest,size_t destsize)803254aed36Seric base64_encode(unsigned char const *src, size_t srclen,
804254aed36Seric char *dest, size_t destsize)
805254aed36Seric {
806254aed36Seric return __b64_ntop(src, srclen, dest, destsize);
807254aed36Seric }
808254aed36Seric
809254aed36Seric int
base64_decode(char const * src,unsigned char * dest,size_t destsize)810254aed36Seric base64_decode(char const *src, unsigned char *dest, size_t destsize)
811254aed36Seric {
812254aed36Seric return __b64_pton(src, dest, destsize);
813254aed36Seric }
814f24248b7Sreyk
8158cfe1040Sgilles int
base64_encode_rfc3548(unsigned char const * src,size_t srclen,char * dest,size_t destsize)8168cfe1040Sgilles base64_encode_rfc3548(unsigned char const *src, size_t srclen,
8178cfe1040Sgilles char *dest, size_t destsize)
8188cfe1040Sgilles {
8198cfe1040Sgilles size_t i;
8208cfe1040Sgilles int ret;
8218cfe1040Sgilles
8228cfe1040Sgilles if ((ret = base64_encode(src, srclen, dest, destsize)) == -1)
8238cfe1040Sgilles return -1;
8248cfe1040Sgilles
8258cfe1040Sgilles for (i = 0; i < destsize; ++i) {
8268cfe1040Sgilles if (dest[i] == '/')
8278cfe1040Sgilles dest[i] = '_';
8288cfe1040Sgilles else if (dest[i] == '+')
8298cfe1040Sgilles dest[i] = '-';
8308cfe1040Sgilles }
8318cfe1040Sgilles
8328cfe1040Sgilles return ret;
8338cfe1040Sgilles }
8348cfe1040Sgilles
835f24248b7Sreyk void
log_trace0(const char * emsg,...)83682e75344Seric log_trace0(const char *emsg, ...)
837f24248b7Sreyk {
838f24248b7Sreyk va_list ap;
839f24248b7Sreyk
840f24248b7Sreyk va_start(ap, emsg);
841f24248b7Sreyk vlog(LOG_DEBUG, emsg, ap);
842f24248b7Sreyk va_end(ap);
843f24248b7Sreyk }
844f24248b7Sreyk
845f24248b7Sreyk void
log_trace_verbose(int v)846f24248b7Sreyk log_trace_verbose(int v)
847f24248b7Sreyk {
848f24248b7Sreyk tracing = v;
849f24248b7Sreyk
850f24248b7Sreyk /* Set debug logging in log.c */
851871fc12cSreyk log_setverbose(v & TRACE_DEBUG ? 2 : foreground_log);
852f24248b7Sreyk }
853d7df8c18Sop
854d7df8c18Sop int
parse_table_line(FILE * fp,char ** line,size_t * linesize,int * type,char ** key,char ** val,int * malformed)855d7df8c18Sop parse_table_line(FILE *fp, char **line, size_t *linesize,
856d7df8c18Sop int *type, char **key, char **val, int *malformed)
857d7df8c18Sop {
8586d4e484eSop char *keyp, *valp;
859d7df8c18Sop ssize_t linelen;
860d7df8c18Sop
861d7df8c18Sop *key = NULL;
862d7df8c18Sop *val = NULL;
863d7df8c18Sop *malformed = 0;
864d7df8c18Sop
865d7df8c18Sop if ((linelen = getline(line, linesize, fp)) == -1)
866d7df8c18Sop return (-1);
867d7df8c18Sop
868d7df8c18Sop keyp = *line;
869d7df8c18Sop while (isspace((unsigned char)*keyp)) {
870d7df8c18Sop ++keyp;
871d7df8c18Sop --linelen;
872d7df8c18Sop }
873d7df8c18Sop if (*keyp == '\0')
874d7df8c18Sop return 0;
875d7df8c18Sop while (linelen > 0 && isspace((unsigned char)keyp[linelen - 1]))
876d7df8c18Sop keyp[--linelen] = '\0';
877d7df8c18Sop if (*keyp == '#') {
878d7df8c18Sop if (*type == T_NONE) {
879d7df8c18Sop keyp++;
880d7df8c18Sop while (isspace((unsigned char)*keyp))
881d7df8c18Sop ++keyp;
882d7df8c18Sop if (!strcmp(keyp, "@list"))
883d7df8c18Sop *type = T_LIST;
884d7df8c18Sop }
885d7df8c18Sop return 0;
886d7df8c18Sop }
887d7df8c18Sop
8886d4e484eSop if (*keyp == '[') {
8896d4e484eSop if ((valp = strchr(keyp, ']')) == NULL) {
8906d4e484eSop *malformed = 1;
8916d4e484eSop return (0);
892d7df8c18Sop }
8936d4e484eSop valp++;
8946d4e484eSop } else
8956d4e484eSop valp = keyp + strcspn(keyp, " \t:");
8966d4e484eSop
897d7df8c18Sop if (*type == T_NONE)
8986d4e484eSop *type = (*valp == '\0') ? T_LIST : T_HASH;
899d7df8c18Sop
900d7df8c18Sop if (*type == T_LIST) {
901d7df8c18Sop *key = keyp;
902d7df8c18Sop return (0);
903d7df8c18Sop }
904d7df8c18Sop
905d7df8c18Sop /* T_HASH */
9066d4e484eSop if (*valp != '\0') {
9076d4e484eSop *valp++ = '\0';
9086d4e484eSop valp += strspn(valp, " \t");
909d7df8c18Sop }
910d7df8c18Sop if (*valp == '\0')
911d7df8c18Sop *malformed = 1;
912d7df8c18Sop
913d7df8c18Sop *key = keyp;
914d7df8c18Sop *val = valp;
915d7df8c18Sop return (0);
916d7df8c18Sop }
917