GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.sbin/smtpd/smtpctl/../util.c Lines: 0 342 0.0 %
Date: 2017-11-13 Branches: 0 283 0.0 %

Line Branch Exec Source
1
/*	$OpenBSD: util.c,v 1.132 2017/01/09 14:49:22 reyk Exp $	*/
2
3
/*
4
 * Copyright (c) 2000,2001 Markus Friedl.  All rights reserved.
5
 * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org>
6
 * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net>
7
 * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
8
 *
9
 * Permission to use, copy, modify, and distribute this software for any
10
 * purpose with or without fee is hereby granted, provided that the above
11
 * copyright notice and this permission notice appear in all copies.
12
 *
13
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20
 */
21
22
#include <sys/types.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
#include <arpa/inet.h>
31
32
#include <ctype.h>
33
#include <errno.h>
34
#include <event.h>
35
#include <fcntl.h>
36
#include <fts.h>
37
#include <imsg.h>
38
#include <inttypes.h>
39
#include <libgen.h>
40
#include <netdb.h>
41
#include <pwd.h>
42
#include <limits.h>
43
#include <resolv.h>
44
#include <stdarg.h>
45
#include <stdio.h>
46
#include <stdlib.h>
47
#include <string.h>
48
#include <time.h>
49
#include <unistd.h>
50
51
#include "smtpd.h"
52
#include "log.h"
53
54
const char *log_in6addr(const struct in6_addr *);
55
const char *log_sockaddr(struct sockaddr *);
56
static int  parse_mailname_file(char *, size_t);
57
58
int	tracing = 0;
59
int	foreground_log = 0;
60
61
void *
62
xmalloc(size_t size, const char *where)
63
{
64
	void	*r;
65
66
	if ((r = malloc(size)) == NULL) {
67
		log_warnx("%s: malloc(%zu)", where, size);
68
		fatalx("exiting");
69
	}
70
71
	return (r);
72
}
73
74
void *
75
xcalloc(size_t nmemb, size_t size, const char *where)
76
{
77
	void	*r;
78
79
	if ((r = calloc(nmemb, size)) == NULL) {
80
		log_warnx("%s: calloc(%zu, %zu)", where, nmemb, size);
81
		fatalx("exiting");
82
	}
83
84
	return (r);
85
}
86
87
char *
88
xstrdup(const char *str, const char *where)
89
{
90
	char	*r;
91
92
	if ((r = strdup(str)) == NULL) {
93
		log_warnx("%s: strdup(%p)", where, str);
94
		fatalx("exiting");
95
	}
96
97
	return (r);
98
}
99
100
void *
101
xmemdup(const void *ptr, size_t size, const char *where)
102
{
103
	void	*r;
104
105
	if ((r = malloc(size)) == NULL) {
106
		log_warnx("%s: malloc(%zu)", where, size);
107
		fatalx("exiting");
108
	}
109
	memmove(r, ptr, size);
110
111
	return (r);
112
}
113
114
#if !defined(NO_IO)
115
int
116
io_xprintf(struct io *io, const char *fmt, ...)
117
{
118
	va_list	ap;
119
	int len;
120
121
	va_start(ap, fmt);
122
	len = io_vprintf(io, fmt, ap);
123
	va_end(ap);
124
	if (len == -1)
125
		fatal("io_xprintf(%p, %s, ...)", io, fmt);
126
127
	return len;
128
}
129
130
int
131
io_xprint(struct io *io, const char *str)
132
{
133
	int len;
134
135
	len = io_print(io, str);
136
	if (len == -1)
137
		fatal("io_xprint(%p, %s, ...)", io, str);
138
139
	return len;
140
}
141
#endif
142
143
char *
144
strip(char *s)
145
{
146
	size_t	 l;
147
148
	while (isspace((unsigned char)*s))
149
		s++;
150
151
	for (l = strlen(s); l; l--) {
152
		if (!isspace((unsigned char)s[l-1]))
153
			break;
154
		s[l-1] = '\0';
155
	}
156
157
	return (s);
158
}
159
160
int
161
bsnprintf(char *str, size_t size, const char *format, ...)
162
{
163
	int ret;
164
	va_list ap;
165
166
	va_start(ap, format);
167
	ret = vsnprintf(str, size, format, ap);
168
	va_end(ap);
169
	if (ret == -1 || ret >= (int)size)
170
		return 0;
171
172
	return 1;
173
}
174
175
176
static int
177
mkdirs_component(char *path, mode_t mode)
178
{
179
	struct stat	sb;
180
181
	if (stat(path, &sb) == -1) {
182
		if (errno != ENOENT)
183
			return 0;
184
		if (mkdir(path, mode | S_IWUSR | S_IXUSR) == -1)
185
			return 0;
186
	}
187
	else if (!S_ISDIR(sb.st_mode))
188
		return 0;
189
190
	return 1;
191
}
192
193
int
194
mkdirs(char *path, mode_t mode)
195
{
196
	char	 buf[PATH_MAX];
197
	int	 i = 0;
198
	int	 done = 0;
199
	char	*p;
200
201
	/* absolute path required */
202
	if (*path != '/')
203
		return 0;
204
205
	/* make sure we don't exceed PATH_MAX */
206
	if (strlen(path) >= sizeof buf)
207
		return 0;
208
209
	memset(buf, 0, sizeof buf);
210
	for (p = path; *p; p++) {
211
		if (*p == '/') {
212
			if (buf[0] != '\0')
213
				if (!mkdirs_component(buf, mode))
214
					return 0;
215
			while (*p == '/')
216
				p++;
217
			buf[i++] = '/';
218
			buf[i++] = *p;
219
			if (*p == '\0' && ++done)
220
				break;
221
			continue;
222
		}
223
		buf[i++] = *p;
224
	}
225
	if (!done)
226
		if (!mkdirs_component(buf, mode))
227
			return 0;
228
229
	if (chmod(path, mode) == -1)
230
		return 0;
231
232
	return 1;
233
}
234
235
int
236
ckdir(const char *path, mode_t mode, uid_t owner, gid_t group, int create)
237
{
238
	char		mode_str[12];
239
	int		ret;
240
	struct stat	sb;
241
242
	if (stat(path, &sb) == -1) {
243
		if (errno != ENOENT || create == 0) {
244
			log_warn("stat: %s", path);
245
			return (0);
246
		}
247
248
		/* chmod is deferred to avoid umask effect */
249
		if (mkdir(path, 0) == -1) {
250
			log_warn("mkdir: %s", path);
251
			return (0);
252
		}
253
254
		if (chown(path, owner, group) == -1) {
255
			log_warn("chown: %s", path);
256
			return (0);
257
		}
258
259
		if (chmod(path, mode) == -1) {
260
			log_warn("chmod: %s", path);
261
			return (0);
262
		}
263
264
		if (stat(path, &sb) == -1) {
265
			log_warn("stat: %s", path);
266
			return (0);
267
		}
268
	}
269
270
	ret = 1;
271
272
	/* check if it's a directory */
273
	if (!S_ISDIR(sb.st_mode)) {
274
		ret = 0;
275
		log_warnx("%s is not a directory", path);
276
	}
277
278
	/* check that it is owned by owner/group */
279
	if (sb.st_uid != owner) {
280
		ret = 0;
281
		log_warnx("%s is not owned by uid %d", path, owner);
282
	}
283
	if (sb.st_gid != group) {
284
		ret = 0;
285
		log_warnx("%s is not owned by gid %d", path, group);
286
	}
287
288
	/* check permission */
289
	if ((sb.st_mode & 07777) != mode) {
290
		ret = 0;
291
		strmode(mode, mode_str);
292
		mode_str[10] = '\0';
293
		log_warnx("%s must be %s (%o)", path, mode_str + 1, mode);
294
	}
295
296
	return ret;
297
}
298
299
int
300
rmtree(char *path, int keepdir)
301
{
302
	char		*path_argv[2];
303
	FTS		*fts;
304
	FTSENT		*e;
305
	int		 ret, depth;
306
307
	path_argv[0] = path;
308
	path_argv[1] = NULL;
309
	ret = 0;
310
	depth = 0;
311
312
	fts = fts_open(path_argv, FTS_PHYSICAL | FTS_NOCHDIR, NULL);
313
	if (fts == NULL) {
314
		log_warn("fts_open: %s", path);
315
		return (-1);
316
	}
317
318
	while ((e = fts_read(fts)) != NULL) {
319
		switch (e->fts_info) {
320
		case FTS_D:
321
			depth++;
322
			break;
323
		case FTS_DP:
324
		case FTS_DNR:
325
			depth--;
326
			if (keepdir && depth == 0)
327
				continue;
328
			if (rmdir(e->fts_path) == -1) {
329
				log_warn("rmdir: %s", e->fts_path);
330
				ret = -1;
331
			}
332
			break;
333
334
		case FTS_F:
335
			if (unlink(e->fts_path) == -1) {
336
				log_warn("unlink: %s", e->fts_path);
337
				ret = -1;
338
			}
339
		}
340
	}
341
342
	fts_close(fts);
343
344
	return (ret);
345
}
346
347
int
348
mvpurge(char *from, char *to)
349
{
350
	size_t		 n;
351
	int		 retry;
352
	const char	*sep;
353
	char		 buf[PATH_MAX];
354
355
	if ((n = strlen(to)) == 0)
356
		fatalx("to is empty");
357
358
	sep = (to[n - 1] == '/') ? "" : "/";
359
	retry = 0;
360
361
again:
362
	(void)snprintf(buf, sizeof buf, "%s%s%u", to, sep, arc4random());
363
	if (rename(from, buf) == -1) {
364
		/* ENOTDIR has actually 2 meanings, and incorrect input
365
		 * could lead to an infinite loop. Consider that after
366
		 * 20 tries something is hopelessly wrong.
367
		 */
368
		if (errno == ENOTEMPTY || errno == EISDIR || errno == ENOTDIR) {
369
			if ((retry++) >= 20)
370
				return (-1);
371
			goto again;
372
		}
373
		return -1;
374
	}
375
376
	return 0;
377
}
378
379
380
int
381
mktmpfile(void)
382
{
383
	char		path[PATH_MAX];
384
	int		fd;
385
386
	if (!bsnprintf(path, sizeof(path), "%s/smtpd.XXXXXXXXXX",
387
		PATH_TEMPORARY)) {
388
		log_warn("snprintf");
389
		fatal("exiting");
390
	}
391
392
	if ((fd = mkstemp(path)) == -1) {
393
		log_warn("cannot create temporary file %s", path);
394
		fatal("exiting");
395
	}
396
	unlink(path);
397
	return (fd);
398
}
399
400
401
/* Close file, signifying temporary error condition (if any) to the caller. */
402
int
403
safe_fclose(FILE *fp)
404
{
405
	if (ferror(fp)) {
406
		fclose(fp);
407
		return 0;
408
	}
409
	if (fflush(fp)) {
410
		fclose(fp);
411
		if (errno == ENOSPC)
412
			return 0;
413
		fatal("safe_fclose: fflush");
414
	}
415
	if (fsync(fileno(fp)))
416
		fatal("safe_fclose: fsync");
417
	if (fclose(fp))
418
		fatal("safe_fclose: fclose");
419
420
	return 1;
421
}
422
423
int
424
hostname_match(const char *hostname, const char *pattern)
425
{
426
	while (*pattern != '\0' && *hostname != '\0') {
427
		if (*pattern == '*') {
428
			while (*pattern == '*')
429
				pattern++;
430
			while (*hostname != '\0' &&
431
			    tolower((unsigned char)*hostname) !=
432
			    tolower((unsigned char)*pattern))
433
				hostname++;
434
			continue;
435
		}
436
437
		if (tolower((unsigned char)*pattern) !=
438
		    tolower((unsigned char)*hostname))
439
			return 0;
440
		pattern++;
441
		hostname++;
442
	}
443
444
	return (*hostname == '\0' && *pattern == '\0');
445
}
446
447
int
448
mailaddr_match(const struct mailaddr *maddr1, const struct mailaddr *maddr2)
449
{
450
	struct mailaddr m1 = *maddr1;
451
	struct mailaddr m2 = *maddr2;
452
	char	       *p;
453
454
	/* catchall */
455
	if (m2.user[0] == '\0' && m2.domain[0] == '\0')
456
		return 1;
457
458
	if (!hostname_match(m1.domain, m2.domain))
459
		return 0;
460
461
	if (m2.user[0]) {
462
		/* if address from table has a tag, we must respect it */
463
		if (strchr(m2.user, *env->sc_subaddressing_delim) == NULL) {
464
			/* otherwise, strip tag from session address if any */
465
			p = strchr(m1.user, *env->sc_subaddressing_delim);
466
			if (p)
467
				*p = '\0';
468
		}
469
		if (strcasecmp(m1.user, m2.user))
470
			return 0;
471
	}
472
	return 1;
473
}
474
475
int
476
valid_localpart(const char *s)
477
{
478
#define IS_ATEXT(c) (isalnum((unsigned char)(c)) || strchr(MAILADDR_ALLOWED, (c)))
479
nextatom:
480
	if (!IS_ATEXT(*s) || *s == '\0')
481
		return 0;
482
	while (*(++s) != '\0') {
483
		if (*s == '.')
484
			break;
485
		if (IS_ATEXT(*s))
486
			continue;
487
		return 0;
488
	}
489
	if (*s == '.') {
490
		s++;
491
		goto nextatom;
492
	}
493
	return 1;
494
}
495
496
int
497
valid_domainpart(const char *s)
498
{
499
	struct in_addr	 ina;
500
	struct in6_addr	 ina6;
501
	char		*c, domain[SMTPD_MAXDOMAINPARTSIZE];
502
	const char	*p;
503
504
	if (*s == '[') {
505
		if (strncasecmp("[IPv6:", s, 6) == 0)
506
			p = s + 6;
507
		else
508
			p = s + 1;
509
510
		if (strlcpy(domain, p, sizeof domain) >= sizeof domain)
511
			return 0;
512
513
		c = strchr(domain, (int)']');
514
		if (!c || c[1] != '\0')
515
			return 0;
516
517
		*c = '\0';
518
519
		if (inet_pton(AF_INET6, domain, &ina6) == 1)
520
			return 1;
521
		if (inet_pton(AF_INET, domain, &ina) == 1)
522
			return 1;
523
524
		return 0;
525
	}
526
527
	if (*s == '\0')
528
		return 0;
529
530
	return res_hnok(s);
531
}
532
533
int
534
secure_file(int fd, char *path, char *userdir, uid_t uid, int mayread)
535
{
536
	char		 buf[PATH_MAX];
537
	char		 homedir[PATH_MAX];
538
	struct stat	 st;
539
	char		*cp;
540
541
	if (realpath(path, buf) == NULL)
542
		return 0;
543
544
	if (realpath(userdir, homedir) == NULL)
545
		homedir[0] = '\0';
546
547
	/* Check the open file to avoid races. */
548
	if (fstat(fd, &st) < 0 ||
549
	    !S_ISREG(st.st_mode) ||
550
	    st.st_uid != uid ||
551
	    (st.st_mode & (mayread ? 022 : 066)) != 0)
552
		return 0;
553
554
	/* For each component of the canonical path, walking upwards. */
555
	for (;;) {
556
		if ((cp = dirname(buf)) == NULL)
557
			return 0;
558
		(void)strlcpy(buf, cp, sizeof(buf));
559
560
		if (stat(buf, &st) < 0 ||
561
		    (st.st_uid != 0 && st.st_uid != uid) ||
562
		    (st.st_mode & 022) != 0)
563
			return 0;
564
565
		/* We can stop checking after reaching homedir level. */
566
		if (strcmp(homedir, buf) == 0)
567
			break;
568
569
		/*
570
		 * dirname should always complete with a "/" path,
571
		 * but we can be paranoid and check for "." too
572
		 */
573
		if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0))
574
			break;
575
	}
576
577
	return 1;
578
}
579
580
void
581
addargs(arglist *args, char *fmt, ...)
582
{
583
	va_list ap;
584
	char *cp;
585
	uint nalloc;
586
	int r;
587
	char	**tmp;
588
589
	va_start(ap, fmt);
590
	r = vasprintf(&cp, fmt, ap);
591
	va_end(ap);
592
	if (r == -1)
593
		fatal("addargs: argument too long");
594
595
	nalloc = args->nalloc;
596
	if (args->list == NULL) {
597
		nalloc = 32;
598
		args->num = 0;
599
	} else if (args->num+2 >= nalloc)
600
		nalloc *= 2;
601
602
	tmp = reallocarray(args->list, nalloc, sizeof(char *));
603
	if (tmp == NULL)
604
		fatal("addargs: reallocarray");
605
	args->list = tmp;
606
	args->nalloc = nalloc;
607
	args->list[args->num++] = cp;
608
	args->list[args->num] = NULL;
609
}
610
611
int
612
lowercase(char *buf, const char *s, size_t len)
613
{
614
	if (len == 0)
615
		return 0;
616
617
	if (strlcpy(buf, s, len) >= len)
618
		return 0;
619
620
	while (*buf != '\0') {
621
		*buf = tolower((unsigned char)*buf);
622
		buf++;
623
	}
624
625
	return 1;
626
}
627
628
int
629
uppercase(char *buf, const char *s, size_t len)
630
{
631
	if (len == 0)
632
		return 0;
633
634
	if (strlcpy(buf, s, len) >= len)
635
		return 0;
636
637
	while (*buf != '\0') {
638
		*buf = toupper((unsigned char)*buf);
639
		buf++;
640
	}
641
642
	return 1;
643
}
644
645
void
646
xlowercase(char *buf, const char *s, size_t len)
647
{
648
	if (len == 0)
649
		fatalx("lowercase: len == 0");
650
651
	if (!lowercase(buf, s, len))
652
		fatalx("lowercase: truncation");
653
}
654
655
uint64_t
656
generate_uid(void)
657
{
658
	static uint32_t id;
659
	static uint8_t	inited;
660
	uint64_t	uid;
661
662
	if (!inited) {
663
		id = arc4random();
664
		inited = 1;
665
	}
666
	while ((uid = ((uint64_t)(id++) << 32 | arc4random())) == 0)
667
		;
668
669
	return (uid);
670
}
671
672
int
673
session_socket_error(int fd)
674
{
675
	int		error;
676
	socklen_t	len;
677
678
	len = sizeof(error);
679
	if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) == -1)
680
		fatal("session_socket_error: getsockopt");
681
682
	return (error);
683
}
684
685
const char *
686
parse_smtp_response(char *line, size_t len, char **msg, int *cont)
687
{
688
	size_t	 i;
689
690
	if (len >= LINE_MAX)
691
		return "line too long";
692
693
	if (len > 3) {
694
		if (msg)
695
			*msg = line + 4;
696
		if (cont)
697
			*cont = (line[3] == '-');
698
	} else if (len == 3) {
699
		if (msg)
700
			*msg = line + 3;
701
		if (cont)
702
			*cont = 0;
703
	} else
704
		return "line too short";
705
706
	/* validate reply code */
707
	if (line[0] < '2' || line[0] > '5' || !isdigit((unsigned char)line[1]) ||
708
	    !isdigit((unsigned char)line[2]))
709
		return "reply code out of range";
710
711
	/* validate reply message */
712
	for (i = 0; i < len; i++)
713
		if (!isprint((unsigned char)line[i]))
714
			return "non-printable character in reply";
715
716
	return NULL;
717
}
718
719
static int
720
parse_mailname_file(char *hostname, size_t len)
721
{
722
	FILE	*fp;
723
	char	*buf = NULL;
724
	size_t	 bufsz = 0;
725
	ssize_t	 buflen;
726
727
	if ((fp = fopen(MAILNAME_FILE, "r")) == NULL)
728
		return 1;
729
730
	if ((buflen = getline(&buf, &bufsz, fp)) == -1)
731
		goto error;
732
733
	if (buf[buflen - 1] == '\n')
734
		buf[buflen - 1] = '\0';
735
736
	if (strlcpy(hostname, buf, len) >= len) {
737
		fprintf(stderr, MAILNAME_FILE " entry too long");
738
		goto error;
739
	}
740
741
	return 0;
742
error:
743
	fclose(fp);
744
	free(buf);
745
	return 1;
746
}
747
748
int
749
getmailname(char *hostname, size_t len)
750
{
751
	struct addrinfo	 hints, *res = NULL;
752
	int		 error;
753
754
	/* Try MAILNAME_FILE first */
755
	if (parse_mailname_file(hostname, len) == 0)
756
		return 0;
757
758
	/* Next, gethostname(3) */
759
	if (gethostname(hostname, len) == -1) {
760
		fprintf(stderr, "getmailname: gethostname() failed\n");
761
		return -1;
762
	}
763
764
	if (strchr(hostname, '.') != NULL)
765
		return 0;
766
767
	/* Canonicalize if domain part is missing */
768
	memset(&hints, 0, sizeof hints);
769
	hints.ai_family = PF_UNSPEC;
770
	hints.ai_flags = AI_CANONNAME;
771
	error = getaddrinfo(hostname, NULL, &hints, &res);
772
	if (error)
773
		return 0; /* Continue with non-canon hostname */
774
775
	if (strlcpy(hostname, res->ai_canonname, len) >= len) {
776
		fprintf(stderr, "hostname too long");
777
		return -1;
778
	}
779
780
	freeaddrinfo(res);
781
	return 0;
782
}
783
784
int
785
base64_encode(unsigned char const *src, size_t srclen,
786
	      char *dest, size_t destsize)
787
{
788
	return __b64_ntop(src, srclen, dest, destsize);
789
}
790
791
int
792
base64_decode(char const *src, unsigned char *dest, size_t destsize)
793
{
794
	return __b64_pton(src, dest, destsize);
795
}
796
797
void
798
log_trace(int mask, const char *emsg, ...)
799
{
800
	va_list	 ap;
801
802
	if (tracing & mask) {
803
		va_start(ap, emsg);
804
		vlog(LOG_DEBUG, emsg, ap);
805
		va_end(ap);
806
	}
807
}
808
809
void
810
log_trace_verbose(int v)
811
{
812
	tracing = v;
813
814
	/* Set debug logging in log.c */
815
	log_setverbose(v & TRACE_DEBUG ? 2 : foreground_log);
816
}