GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.sbin/smtpd/smtpctl/../to.c Lines: 15 437 3.4 %
Date: 2017-11-07 Branches: 2 304 0.7 %

Line Branch Exec Source
1
/*	$OpenBSD: to.c,v 1.28 2016/05/30 12:33:44 mpi Exp $	*/
2
3
/*
4
 * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net>
5
 * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
6
 * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org>
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/queue.h>
23
#include <sys/tree.h>
24
#include <sys/socket.h>
25
#include <sys/stat.h>
26
#include <sys/resource.h>
27
28
#include <netinet/in.h>
29
#include <arpa/inet.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 <limits.h>
38
#include <inttypes.h>
39
#include <libgen.h>
40
#include <netdb.h>
41
#include <pwd.h>
42
#include <stdarg.h>
43
#include <stdio.h>
44
#include <stdlib.h>
45
#include <string.h>
46
#include <time.h>
47
#include <unistd.h>
48
49
#include "smtpd.h"
50
#include "log.h"
51
52
static const char *in6addr_to_text(const struct in6_addr *);
53
static int alias_is_maildir(struct expandnode *, const char *, size_t);
54
static int alias_is_filter(struct expandnode *, const char *, size_t);
55
static int alias_is_username(struct expandnode *, const char *, size_t);
56
static int alias_is_address(struct expandnode *, const char *, size_t);
57
static int alias_is_filename(struct expandnode *, const char *, size_t);
58
static int alias_is_include(struct expandnode *, const char *, size_t);
59
static int alias_is_error(struct expandnode *, const char *, size_t);
60
61
const char *
62
sockaddr_to_text(struct sockaddr *sa)
63
{
64
	static char	buf[NI_MAXHOST];
65
66
	if (getnameinfo(sa, sa->sa_len, buf, sizeof(buf), NULL, 0,
67
	    NI_NUMERICHOST))
68
		return ("(unknown)");
69
	else
70
		return (buf);
71
}
72
73
static const char *
74
in6addr_to_text(const struct in6_addr *addr)
75
{
76
	struct sockaddr_in6	sa_in6;
77
	uint16_t		tmp16;
78
79
	memset(&sa_in6, 0, sizeof(sa_in6));
80
	sa_in6.sin6_len = sizeof(sa_in6);
81
	sa_in6.sin6_family = AF_INET6;
82
	memcpy(&sa_in6.sin6_addr, addr, sizeof(sa_in6.sin6_addr));
83
84
	/* XXX thanks, KAME, for this ugliness... adopted from route/show.c */
85
	if (IN6_IS_ADDR_LINKLOCAL(&sa_in6.sin6_addr) ||
86
	    IN6_IS_ADDR_MC_LINKLOCAL(&sa_in6.sin6_addr)) {
87
		memcpy(&tmp16, &sa_in6.sin6_addr.s6_addr[2], sizeof(tmp16));
88
		sa_in6.sin6_scope_id = ntohs(tmp16);
89
		sa_in6.sin6_addr.s6_addr[2] = 0;
90
		sa_in6.sin6_addr.s6_addr[3] = 0;
91
	}
92
93
	return (sockaddr_to_text((struct sockaddr *)&sa_in6));
94
}
95
96
int
97
text_to_mailaddr(struct mailaddr *maddr, const char *email)
98
{
99
	char *username;
100
	char *hostname;
101
	char  buffer[LINE_MAX];
102
103
	if (strlcpy(buffer, email, sizeof buffer) >= sizeof buffer)
104
		return 0;
105
106
	memset(maddr, 0, sizeof *maddr);
107
108
	username = buffer;
109
	hostname = strrchr(username, '@');
110
111
	if (hostname == NULL) {
112
		if (strlcpy(maddr->user, username, sizeof maddr->user)
113
		    >= sizeof maddr->user)
114
			return 0;
115
	}
116
	else if (username == hostname) {
117
		*hostname++ = '\0';
118
		if (strlcpy(maddr->domain, hostname, sizeof maddr->domain)
119
		    >= sizeof maddr->domain)
120
			return 0;
121
	}
122
	else {
123
		*hostname++ = '\0';
124
		if (strlcpy(maddr->user, username, sizeof maddr->user)
125
		    >= sizeof maddr->user)
126
			return 0;
127
		if (strlcpy(maddr->domain, hostname, sizeof maddr->domain)
128
		    >= sizeof maddr->domain)
129
			return 0;
130
	}
131
132
	return 1;
133
}
134
135
const char *
136
mailaddr_to_text(const struct mailaddr *maddr)
137
{
138
	static char  buffer[LINE_MAX];
139
140
	(void)strlcpy(buffer, maddr->user, sizeof buffer);
141
	(void)strlcat(buffer, "@", sizeof buffer);
142
	if (strlcat(buffer, maddr->domain, sizeof buffer) >= sizeof buffer)
143
		return NULL;
144
145
	return buffer;
146
}
147
148
149
const char *
150
sa_to_text(const struct sockaddr *sa)
151
{
152
	static char	 buf[NI_MAXHOST + 5];
153
	char		*p;
154
155
	buf[0] = '\0';
156
	p = buf;
157
158
	if (sa->sa_family == AF_LOCAL)
159
		(void)strlcpy(buf, "local", sizeof buf);
160
	else if (sa->sa_family == AF_INET) {
161
		in_addr_t addr;
162
163
		addr = ((const struct sockaddr_in *)sa)->sin_addr.s_addr;
164
		addr = ntohl(addr);
165
		(void)bsnprintf(p, NI_MAXHOST, "%d.%d.%d.%d",
166
		    (addr >> 24) & 0xff, (addr >> 16) & 0xff,
167
		    (addr >> 8) & 0xff, addr & 0xff);
168
	}
169
	else if (sa->sa_family == AF_INET6) {
170
		const struct sockaddr_in6 *in6;
171
		const struct in6_addr	*in6_addr;
172
173
		in6 = (const struct sockaddr_in6 *)sa;
174
		(void)strlcpy(buf, "IPv6:", sizeof(buf));
175
		p = buf + 5;
176
		in6_addr = &in6->sin6_addr;
177
		(void)bsnprintf(p, NI_MAXHOST, "%s", in6addr_to_text(in6_addr));
178
	}
179
180
	return (buf);
181
}
182
183
const char *
184
ss_to_text(const struct sockaddr_storage *ss)
185
{
186
	return (sa_to_text((const struct sockaddr*)ss));
187
}
188
189
const char *
190
time_to_text(time_t when)
191
{
192
	struct tm *lt;
193
	static char buf[40];
194
2
	char *day[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
195
2
	char *month[] = {"Jan","Feb","Mar","Apr","May","Jun",
196
			 "Jul","Aug","Sep","Oct","Nov","Dec"};
197
	char *tz;
198
	long offset;
199
200
2
	lt = localtime(&when);
201
2
	if (lt == NULL || when == 0)
202
		fatalx("time_to_text: localtime");
203
204
2
	offset = lt->tm_gmtoff;
205
2
	tz = lt->tm_zone;
206
207
	/* We do not use strftime because it is subject to locale substitution*/
208
2
	if (!bsnprintf(buf, sizeof(buf),
209
	    "%s, %d %s %d %02d:%02d:%02d %c%02d%02d (%s)",
210
2
	    day[lt->tm_wday], lt->tm_mday, month[lt->tm_mon],
211
2
	    lt->tm_year + 1900,
212
2
	    lt->tm_hour, lt->tm_min, lt->tm_sec,
213
2
	    offset >= 0 ? '+' : '-',
214
2
	    abs((int)offset / 3600),
215
2
	    abs((int)offset % 3600) / 60,
216
	    tz))
217
		fatalx("time_to_text: bsnprintf");
218
219
2
	return buf;
220
2
}
221
222
const char *
223
duration_to_text(time_t t)
224
{
225
	static char	dst[64];
226
	char		buf[64];
227
	int		h, m, s;
228
	long long	d;
229
230
	if (t == 0) {
231
		(void)strlcpy(dst, "0s", sizeof dst);
232
		return (dst);
233
	}
234
235
	dst[0] = '\0';
236
	if (t < 0) {
237
		(void)strlcpy(dst, "-", sizeof dst);
238
		t = -t;
239
	}
240
241
	s = t % 60;
242
	t /= 60;
243
	m = t % 60;
244
	t /= 60;
245
	h = t % 24;
246
	d = t / 24;
247
248
	if (d) {
249
		(void)snprintf(buf, sizeof buf, "%lldd", d);
250
		(void)strlcat(dst, buf, sizeof dst);
251
	}
252
	if (h) {
253
		(void)snprintf(buf, sizeof buf, "%dh", h);
254
		(void)strlcat(dst, buf, sizeof dst);
255
	}
256
	if (m) {
257
		(void)snprintf(buf, sizeof buf, "%dm", m);
258
		(void)strlcat(dst, buf, sizeof dst);
259
	}
260
	if (s) {
261
		(void)snprintf(buf, sizeof buf, "%ds", s);
262
		(void)strlcat(dst, buf, sizeof dst);
263
	}
264
265
	return (dst);
266
}
267
268
int
269
text_to_netaddr(struct netaddr *netaddr, const char *s)
270
{
271
	struct sockaddr_storage	ss;
272
	struct sockaddr_in	ssin;
273
	struct sockaddr_in6	ssin6;
274
	int			bits;
275
276
	memset(&ssin, 0, sizeof(struct sockaddr_in));
277
	memset(&ssin6, 0, sizeof(struct sockaddr_in6));
278
279
	if (strncasecmp("IPv6:", s, 5) == 0)
280
		s += 5;
281
282
	bits = inet_net_pton(AF_INET, s, &ssin.sin_addr,
283
	    sizeof(struct in_addr));
284
	if (bits != -1) {
285
		ssin.sin_family = AF_INET;
286
		memcpy(&ss, &ssin, sizeof(ssin));
287
		ss.ss_len = sizeof(struct sockaddr_in);
288
	} else {
289
		bits = inet_net_pton(AF_INET6, s, &ssin6.sin6_addr,
290
		    sizeof(struct in6_addr));
291
		if (bits == -1)
292
			return 0;
293
		ssin6.sin6_family = AF_INET6;
294
		memcpy(&ss, &ssin6, sizeof(ssin6));
295
		ss.ss_len = sizeof(struct sockaddr_in6);
296
	}
297
298
	netaddr->ss   = ss;
299
	netaddr->bits = bits;
300
	return 1;
301
}
302
303
int
304
text_to_relayhost(struct relayhost *relay, const char *s)
305
{
306
	static const struct schema {
307
		const char	*name;
308
		uint16_t       	 flags;
309
	} schemas [] = {
310
		/*
311
		 * new schemas should be *appended* otherwise the default
312
		 * schema index needs to be updated later in this function.
313
		 */
314
		{ "smtp://",		0				},
315
		{ "lmtp://",		F_LMTP				},
316
		{ "smtp+tls://",       	F_TLS_OPTIONAL 			},
317
		{ "smtps://",		F_SMTPS				},
318
		{ "tls://",		F_STARTTLS			},
319
		{ "smtps+auth://",	F_SMTPS|F_AUTH			},
320
		{ "tls+auth://",	F_STARTTLS|F_AUTH		},
321
		{ "secure://",		F_SMTPS|F_STARTTLS		},
322
		{ "secure+auth://",	F_SMTPS|F_STARTTLS|F_AUTH	},
323
		{ "backup://",		F_BACKUP       			},
324
		{ "tls+backup://",	F_BACKUP|F_STARTTLS    		}
325
	};
326
	const char     *errstr = NULL;
327
	char	       *p, *q;
328
	char		buffer[1024];
329
	char	       *beg, *end;
330
	size_t		i;
331
	size_t		len;
332
333
	memset(buffer, 0, sizeof buffer);
334
	if (strlcpy(buffer, s, sizeof buffer) >= sizeof buffer)
335
		return 0;
336
337
	for (i = 0; i < nitems(schemas); ++i)
338
		if (strncasecmp(schemas[i].name, s,
339
		    strlen(schemas[i].name)) == 0)
340
			break;
341
342
	if (i == nitems(schemas)) {
343
		/* there is a schema, but it's not recognized */
344
		if (strstr(buffer, "://"))
345
			return 0;
346
347
		/* no schema, default to smtp+tls:// */
348
		i = 2;
349
		p = buffer;
350
	}
351
	else
352
		p = buffer + strlen(schemas[i].name);
353
354
	relay->flags = schemas[i].flags;
355
356
	/* need to specify an explicit port for LMTP */
357
	if (relay->flags & F_LMTP)
358
		relay->port = 0;
359
360
	/* first, we extract the label if any */
361
	if ((q = strchr(p, '@')) != NULL) {
362
		*q = 0;
363
		if (strlcpy(relay->authlabel, p, sizeof (relay->authlabel))
364
		    >= sizeof (relay->authlabel))
365
			return 0;
366
		p = q + 1;
367
	}
368
369
	/* then, we extract the mail exchanger */
370
	beg = end = p;
371
	if (*beg == '[') {
372
		if ((end = strchr(beg, ']')) == NULL)
373
			return 0;
374
		/* skip ']', it has to be included in the relay hostname */
375
		++end;
376
		len = end - beg;
377
	}
378
	else {
379
		for (end = beg; *end; ++end)
380
			if (!isalnum((unsigned char)*end) &&
381
			    *end != '_' && *end != '.' && *end != '-')
382
				break;
383
		len = end - beg;
384
	}
385
	if (len >= sizeof relay->hostname)
386
		return 0;
387
	for (i = 0; i < len; ++i)
388
		relay->hostname[i] = beg[i];
389
	relay->hostname[i] = 0;
390
391
	/* finally, we extract the port */
392
	p = beg + len;
393
	if (*p == ':') {
394
		relay->port = strtonum(p+1, 1, 0xffff, &errstr);
395
		if (errstr)
396
			return 0;
397
	}
398
399
	if (!valid_domainpart(relay->hostname))
400
		return 0;
401
	if ((relay->flags & F_LMTP) && (relay->port == 0))
402
		return 0;
403
	if (relay->authlabel[0] == '\0' && relay->flags & F_AUTH)
404
		return 0;
405
	if (relay->authlabel[0] != '\0' && !(relay->flags & F_AUTH))
406
		return 0;
407
	return 1;
408
}
409
410
const char *
411
relayhost_to_text(const struct relayhost *relay)
412
{
413
	static char	buf[4096];
414
	char		port[4096];
415
	uint16_t	mask = F_SMTPS|F_STARTTLS|F_AUTH|F_TLS_OPTIONAL|F_LMTP|F_BACKUP;
416
417
	memset(buf, 0, sizeof buf);
418
	switch (relay->flags & mask) {
419
	case F_SMTPS|F_STARTTLS|F_AUTH:
420
		(void)strlcat(buf, "secure+auth://", sizeof buf);
421
		break;
422
	case F_SMTPS|F_STARTTLS:
423
		(void)strlcat(buf, "secure://", sizeof buf);
424
		break;
425
	case F_STARTTLS|F_AUTH:
426
		(void)strlcat(buf, "tls+auth://", sizeof buf);
427
		break;
428
	case F_SMTPS|F_AUTH:
429
		(void)strlcat(buf, "smtps+auth://", sizeof buf);
430
		break;
431
	case F_STARTTLS:
432
		(void)strlcat(buf, "tls://", sizeof buf);
433
		break;
434
	case F_SMTPS:
435
		(void)strlcat(buf, "smtps://", sizeof buf);
436
		break;
437
	case F_BACKUP|F_STARTTLS:
438
		(void)strlcat(buf, "tls+backup://", sizeof buf);
439
		break;
440
	case F_BACKUP:
441
		(void)strlcat(buf, "backup://", sizeof buf);
442
		break;
443
	case F_TLS_OPTIONAL:
444
		(void)strlcat(buf, "smtp+tls://", sizeof buf);
445
		break;
446
	case F_LMTP:
447
		(void)strlcat(buf, "lmtp://", sizeof buf);
448
		break;
449
	default:
450
		(void)strlcat(buf, "smtp://", sizeof buf);
451
		break;
452
	}
453
	if (relay->authlabel[0]) {
454
		(void)strlcat(buf, relay->authlabel, sizeof buf);
455
		(void)strlcat(buf, "@", sizeof buf);
456
	}
457
	(void)strlcat(buf, relay->hostname, sizeof buf);
458
	if (relay->port) {
459
		(void)strlcat(buf, ":", sizeof buf);
460
		(void)snprintf(port, sizeof port, "%d", relay->port);
461
		(void)strlcat(buf, port, sizeof buf);
462
	}
463
	return buf;
464
}
465
466
uint64_t
467
text_to_evpid(const char *s)
468
{
469
	uint64_t ulval;
470
	char	 *ep;
471
472
	errno = 0;
473
	ulval = strtoull(s, &ep, 16);
474
	if (s[0] == '\0' || *ep != '\0')
475
		return 0;
476
	if (errno == ERANGE && ulval == ULLONG_MAX)
477
		return 0;
478
	if (ulval == 0)
479
		return 0;
480
	return (ulval);
481
}
482
483
uint32_t
484
text_to_msgid(const char *s)
485
{
486
	uint64_t ulval;
487
	char	 *ep;
488
489
	errno = 0;
490
	ulval = strtoull(s, &ep, 16);
491
	if (s[0] == '\0' || *ep != '\0')
492
		return 0;
493
	if (errno == ERANGE && ulval == ULLONG_MAX)
494
		return 0;
495
	if (ulval == 0)
496
		return 0;
497
	if (ulval > 0xffffffff)
498
		return 0;
499
	return (ulval & 0xffffffff);
500
}
501
502
const char *
503
rule_to_text(struct rule *r)
504
{
505
	static char buf[4096];
506
507
	memset(buf, 0, sizeof buf);
508
	(void)strlcpy(buf, r->r_decision == R_ACCEPT  ? "accept" : "reject", sizeof buf);
509
	if (r->r_tag[0]) {
510
		(void)strlcat(buf, " tagged ", sizeof buf);
511
		if (r->r_nottag)
512
			(void)strlcat(buf, "! ", sizeof buf);
513
		(void)strlcat(buf, r->r_tag, sizeof buf);
514
	}
515
	(void)strlcat(buf, " from ", sizeof buf);
516
	if (r->r_notsources)
517
		(void)strlcat(buf, "! ", sizeof buf);
518
	(void)strlcat(buf, r->r_sources->t_name, sizeof buf);
519
520
	(void)strlcat(buf, " for ", sizeof buf);
521
	if (r->r_notdestination)
522
		(void)strlcat(buf, "! ", sizeof buf);
523
	switch (r->r_desttype) {
524
	case DEST_DOM:
525
		if (r->r_destination == NULL) {
526
			(void)strlcat(buf, " any", sizeof buf);
527
			break;
528
		}
529
		(void)strlcat(buf, " domain ", sizeof buf);
530
		(void)strlcat(buf, r->r_destination->t_name, sizeof buf);
531
		if (r->r_mapping) {
532
			(void)strlcat(buf, " alias ", sizeof buf);
533
			(void)strlcat(buf, r->r_mapping->t_name, sizeof buf);
534
		}
535
		break;
536
	case DEST_VDOM:
537
		if (r->r_destination == NULL) {
538
			(void)strlcat(buf, " any virtual ", sizeof buf);
539
			(void)strlcat(buf, r->r_mapping->t_name, sizeof buf);
540
			break;
541
		}
542
		(void)strlcat(buf, " domain ", sizeof buf);
543
		(void)strlcat(buf, r->r_destination->t_name, sizeof buf);
544
		(void)strlcat(buf, " virtual ", sizeof buf);
545
		(void)strlcat(buf, r->r_mapping->t_name, sizeof buf);
546
		break;
547
	}
548
549
	switch (r->r_action) {
550
	case A_RELAY:
551
		(void)strlcat(buf, " relay", sizeof buf);
552
		break;
553
	case A_RELAYVIA:
554
		(void)strlcat(buf, " relay via ", sizeof buf);
555
		(void)strlcat(buf, relayhost_to_text(&r->r_value.relayhost), sizeof buf);
556
		break;
557
	case A_MAILDIR:
558
		(void)strlcat(buf, " deliver to maildir \"", sizeof buf);
559
		(void)strlcat(buf, r->r_value.buffer, sizeof buf);
560
		(void)strlcat(buf, "\"", sizeof buf);
561
		break;
562
	case A_MBOX:
563
		(void)strlcat(buf, " deliver to mbox", sizeof buf);
564
		break;
565
	case A_FILENAME:
566
		(void)strlcat(buf, " deliver to filename \"", sizeof buf);
567
		(void)strlcat(buf, r->r_value.buffer, sizeof buf);
568
		(void)strlcat(buf, "\"", sizeof buf);
569
		break;
570
	case A_MDA:
571
		(void)strlcat(buf, " deliver to mda \"", sizeof buf);
572
		(void)strlcat(buf, r->r_value.buffer, sizeof buf);
573
		(void)strlcat(buf, "\"", sizeof buf);
574
		break;
575
	case A_LMTP:
576
		(void)strlcat(buf, " deliver to lmtp \"", sizeof buf);
577
		(void)strlcat(buf, r->r_value.buffer, sizeof buf);
578
		(void)strlcat(buf, "\"", sizeof buf);
579
		break;
580
	case A_NONE:
581
		break;
582
	}
583
584
	return buf;
585
}
586
587
int
588
text_to_userinfo(struct userinfo *userinfo, const char *s)
589
{
590
	char		buf[PATH_MAX];
591
	char	       *p;
592
	const char     *errstr;
593
594
	memset(buf, 0, sizeof buf);
595
	p = buf;
596
	while (*s && *s != ':')
597
		*p++ = *s++;
598
	if (*s++ != ':')
599
		goto error;
600
601
	if (strlcpy(userinfo->username, buf,
602
		sizeof userinfo->username) >= sizeof userinfo->username)
603
		goto error;
604
605
	memset(buf, 0, sizeof buf);
606
	p = buf;
607
	while (*s && *s != ':')
608
		*p++ = *s++;
609
	if (*s++ != ':')
610
		goto error;
611
	userinfo->uid = strtonum(buf, 0, UID_MAX, &errstr);
612
	if (errstr)
613
		goto error;
614
615
	memset(buf, 0, sizeof buf);
616
	p = buf;
617
	while (*s && *s != ':')
618
		*p++ = *s++;
619
	if (*s++ != ':')
620
		goto error;
621
	userinfo->gid = strtonum(buf, 0, GID_MAX, &errstr);
622
	if (errstr)
623
		goto error;
624
625
	if (strlcpy(userinfo->directory, s,
626
		sizeof userinfo->directory) >= sizeof userinfo->directory)
627
		goto error;
628
629
	return 1;
630
631
error:
632
	return 0;
633
}
634
635
int
636
text_to_credentials(struct credentials *creds, const char *s)
637
{
638
	char   *p;
639
	char	buffer[LINE_MAX];
640
	size_t	offset;
641
642
	p = strchr(s, ':');
643
	if (p == NULL) {
644
		creds->username[0] = '\0';
645
		if (strlcpy(creds->password, s, sizeof creds->password)
646
		    >= sizeof creds->password)
647
			return 0;
648
		return 1;
649
	}
650
651
	offset = p - s;
652
653
	memset(buffer, 0, sizeof buffer);
654
	if (strlcpy(buffer, s, sizeof buffer) >= sizeof buffer)
655
		return 0;
656
	p = buffer + offset;
657
	*p = '\0';
658
659
	if (strlcpy(creds->username, buffer, sizeof creds->username)
660
	    >= sizeof creds->username)
661
		return 0;
662
	if (strlcpy(creds->password, p+1, sizeof creds->password)
663
	    >= sizeof creds->password)
664
		return 0;
665
666
	return 1;
667
}
668
669
int
670
text_to_expandnode(struct expandnode *expandnode, const char *s)
671
{
672
	size_t	l;
673
674
	l = strlen(s);
675
	if (alias_is_error(expandnode, s, l) ||
676
	    alias_is_include(expandnode, s, l) ||
677
	    alias_is_filter(expandnode, s, l) ||
678
	    alias_is_filename(expandnode, s, l) ||
679
	    alias_is_address(expandnode, s, l) ||
680
	    alias_is_maildir(expandnode, s, l) ||
681
	    alias_is_username(expandnode, s, l))
682
		return (1);
683
684
	return (0);
685
}
686
687
const char *
688
expandnode_to_text(struct expandnode *expandnode)
689
{
690
	switch (expandnode->type) {
691
	case EXPAND_FILTER:
692
	case EXPAND_FILENAME:
693
	case EXPAND_INCLUDE:
694
	case EXPAND_ERROR:
695
	case EXPAND_MAILDIR:
696
		return expandnode->u.buffer;
697
	case EXPAND_USERNAME:
698
		return expandnode->u.user;
699
	case EXPAND_ADDRESS:
700
		return mailaddr_to_text(&expandnode->u.mailaddr);
701
	case EXPAND_INVALID:
702
		break;
703
	}
704
705
	return NULL;
706
}
707
708
static int
709
alias_is_maildir(struct expandnode *alias, const char *line, size_t len)
710
{
711
	if (strncasecmp("maildir:", line, 8) != 0)
712
		return (0);
713
714
	line += 8;
715
	memset(alias, 0, sizeof *alias);
716
	alias->type = EXPAND_MAILDIR;
717
	if (strlcpy(alias->u.buffer, line,
718
	    sizeof(alias->u.buffer)) >= sizeof(alias->u.buffer))
719
		return (0);
720
721
	return (1);
722
}
723
724
/******/
725
static int
726
alias_is_filter(struct expandnode *alias, const char *line, size_t len)
727
{
728
	int	v = 0;
729
730
	if (*line == '"')
731
		v = 1;
732
	if (*(line+v) == '|') {
733
		if (strlcpy(alias->u.buffer, line + v + 1,
734
		    sizeof(alias->u.buffer)) >= sizeof(alias->u.buffer))
735
			return 0;
736
		if (v) {
737
			v = strlen(alias->u.buffer);
738
			if (v == 0)
739
				return (0);
740
			if (alias->u.buffer[v-1] != '"')
741
				return (0);
742
			alias->u.buffer[v-1] = '\0';
743
		}
744
		alias->type = EXPAND_FILTER;
745
		return (1);
746
	}
747
	return (0);
748
}
749
750
static int
751
alias_is_username(struct expandnode *alias, const char *line, size_t len)
752
{
753
	memset(alias, 0, sizeof *alias);
754
755
	if (strlcpy(alias->u.user, line,
756
	    sizeof(alias->u.user)) >= sizeof(alias->u.user))
757
		return 0;
758
759
	while (*line) {
760
		if (!isalnum((unsigned char)*line) &&
761
		    *line != '_' && *line != '.' && *line != '-' && *line != '+')
762
			return 0;
763
		++line;
764
	}
765
766
	alias->type = EXPAND_USERNAME;
767
	return 1;
768
}
769
770
static int
771
alias_is_address(struct expandnode *alias, const char *line, size_t len)
772
{
773
	char *domain;
774
775
	memset(alias, 0, sizeof *alias);
776
777
	if (len < 3)	/* x@y */
778
		return 0;
779
780
	domain = strchr(line, '@');
781
	if (domain == NULL)
782
		return 0;
783
784
	/* @ cannot start or end an address */
785
	if (domain == line || domain == line + len - 1)
786
		return 0;
787
788
	/* scan pre @ for disallowed chars */
789
	*domain++ = '\0';
790
	(void)strlcpy(alias->u.mailaddr.user, line, sizeof(alias->u.mailaddr.user));
791
	(void)strlcpy(alias->u.mailaddr.domain, domain,
792
	    sizeof(alias->u.mailaddr.domain));
793
794
	while (*line) {
795
		char allowedset[] = "!#$%*/?|^{}`~&'+-=_.";
796
		if (!isalnum((unsigned char)*line) &&
797
		    strchr(allowedset, *line) == NULL)
798
			return 0;
799
		++line;
800
	}
801
802
	while (*domain) {
803
		char allowedset[] = "-.";
804
		if (!isalnum((unsigned char)*domain) &&
805
		    strchr(allowedset, *domain) == NULL)
806
			return 0;
807
		++domain;
808
	}
809
810
	alias->type = EXPAND_ADDRESS;
811
	return 1;
812
}
813
814
static int
815
alias_is_filename(struct expandnode *alias, const char *line, size_t len)
816
{
817
	memset(alias, 0, sizeof *alias);
818
819
	if (*line != '/')
820
		return 0;
821
822
	if (strlcpy(alias->u.buffer, line,
823
	    sizeof(alias->u.buffer)) >= sizeof(alias->u.buffer))
824
		return 0;
825
	alias->type = EXPAND_FILENAME;
826
	return 1;
827
}
828
829
static int
830
alias_is_include(struct expandnode *alias, const char *line, size_t len)
831
{
832
	size_t skip;
833
834
	memset(alias, 0, sizeof *alias);
835
836
	if (strncasecmp(":include:", line, 9) == 0)
837
		skip = 9;
838
	else if (strncasecmp("include:", line, 8) == 0)
839
		skip = 8;
840
	else
841
		return 0;
842
843
	if (!alias_is_filename(alias, line + skip, len - skip))
844
		return 0;
845
846
	alias->type = EXPAND_INCLUDE;
847
	return 1;
848
}
849
850
static int
851
alias_is_error(struct expandnode *alias, const char *line, size_t len)
852
{
853
	size_t	skip;
854
855
	memset(alias, 0, sizeof *alias);
856
857
	if (strncasecmp(":error:", line, 7) == 0)
858
		skip = 7;
859
	else if (strncasecmp("error:", line, 6) == 0)
860
		skip = 6;
861
	else
862
		return 0;
863
864
	if (strlcpy(alias->u.buffer, line + skip,
865
	    sizeof(alias->u.buffer)) >= sizeof(alias->u.buffer))
866
		return 0;
867
868
	if (strlen(alias->u.buffer) < 5)
869
		return 0;
870
871
	/* [45][0-9]{2} [a-zA-Z0-9].* */
872
	if (alias->u.buffer[3] != ' ' ||
873
	    !isalnum((unsigned char)alias->u.buffer[4]) ||
874
	    (alias->u.buffer[0] != '4' && alias->u.buffer[0] != '5') ||
875
	    !isdigit((unsigned char)alias->u.buffer[1]) ||
876
	    !isdigit((unsigned char)alias->u.buffer[2]))
877
		return 0;
878
879
	alias->type = EXPAND_ERROR;
880
	return 1;
881
}