1184870Syongari/*
2184870Syongari * Copyright (c) 2017 Gilles Chehade <gilles@poolp.org>
3184870Syongari *
4184870Syongari * Permission to use, copy, modify, and distribute this software for any
5184870Syongari * purpose with or without fee is hereby granted, provided that the above
6184870Syongari * copyright notice and this permission notice appear in all copies.
7184870Syongari *
8184870Syongari * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9184870Syongari * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10184870Syongari * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11184870Syongari * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12184870Syongari * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13184870Syongari * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14184870Syongari * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15184870Syongari */
16184870Syongari
17184870Syongari#ifndef nitems
18184870Syongari#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
19184870Syongari#endif
20184870Syongari
21184870Syongari#include <sys/stat.h>
22184870Syongari
23184870Syongari#include <err.h>
24184870Syongari#include <errno.h>
25184870Syongari#include <fcntl.h>
26184870Syongari#include <limits.h>
27184870Syongari#include <netdb.h>
28184870Syongari#include <stdio.h>
29184870Syongari#include <stdlib.h>
30184870Syongari#include <string.h>
31184870Syongari#include <sysexits.h>
32184870Syongari#include <time.h>
33184870Syongari#include <unistd.h>
34184870Syongari
35184870Syongari#define	MAILADDR_ESCAPE		"!#$%&'*/?^`{|}~"
36184870Syongari
37184870Syongaristatic int	maildir_subdir(const char *, char *, size_t);
38184870Syongaristatic void	maildir_mkdirs(const char *);
39184870Syongaristatic void	maildir_engine(const char *, int);
40184870Syongaristatic int	mkdirs_component(const char *, mode_t);
41184870Syongaristatic int	mkdirs(const char *, mode_t);
42184870Syongari
43264443Syongariint
44184870Syongarimain(int argc, char *argv[])
45184870Syongari{
46184870Syongari	int	ch;
47184870Syongari	int	junk = 0;
48184870Syongari
49184870Syongari	if (! geteuid())
50184870Syongari		errx(1, "mail.maildir: may not be executed as root");
51184870Syongari
52184870Syongari	while ((ch = getopt(argc, argv, "j")) != -1) {
53184870Syongari		switch (ch) {
54184870Syongari		case 'j':
55184870Syongari			junk = 1;
56184870Syongari			break;
57184870Syongari		default:
58184870Syongari			break;
59184870Syongari		}
60184870Syongari	}
61184870Syongari	argc -= optind;
62184870Syongari	argv += optind;
63184870Syongari
64184870Syongari	if (argc > 1)
65184870Syongari		errx(1, "mail.maildir: only one maildir is allowed");
66184870Syongari
67184870Syongari	maildir_engine(argv[0], junk);
68184870Syongari
69184870Syongari	return (0);
70184870Syongari}
71184870Syongari
72184870Syongaristatic int
73184870Syongarimaildir_subdir(const char *extension, char *dest, size_t len)
74184870Syongari{
75184870Syongari	char		*sanitized;
76184870Syongari
77184870Syongari	if (strlcpy(dest, extension, len) >= len)
78184870Syongari		return 0;
79184870Syongari
80184870Syongari	for (sanitized = dest; *sanitized; sanitized++)
81184870Syongari		if (strchr(MAILADDR_ESCAPE, *sanitized))
82184870Syongari			*sanitized = ':';
83184870Syongari
84184870Syongari	return 1;
85184870Syongari}
86184870Syongari
87184870Syongaristatic void
88184870Syongarimaildir_mkdirs(const char *dirname)
89184870Syongari{
90184870Syongari	uint	i;
91184870Syongari	int	ret;
92184870Syongari	char	pathname[PATH_MAX];
93184870Syongari	char	*subdirs[] = { "cur", "tmp", "new" };
94184870Syongari
95184870Syongari	if (mkdirs(dirname, 0700) == -1 && errno != EEXIST) {
96184870Syongari		if (errno == EINVAL || errno == ENAMETOOLONG)
97184870Syongari			err(1, NULL);
98184870Syongari		err(EX_TEMPFAIL, NULL);
99184870Syongari	}
100184870Syongari
101184870Syongari	for (i = 0; i < nitems(subdirs); ++i) {
102184870Syongari		ret = snprintf(pathname, sizeof pathname, "%s/%s", dirname,
103184870Syongari		    subdirs[i]);
104184870Syongari		if (ret < 0 || (size_t)ret >= sizeof pathname)
105184870Syongari			errc(1, ENAMETOOLONG, "%s/%s", dirname, subdirs[i]);
106184870Syongari		if (mkdir(pathname, 0700) == -1 && errno != EEXIST)
107184870Syongari			err(EX_TEMPFAIL, NULL);
108184870Syongari	}
109184870Syongari}
110184870Syongari
111184870Syongaristatic void
112184870Syongarimaildir_engine(const char *dirname, int junk)
113184870Syongari{
114184870Syongari	char	rootpath[PATH_MAX];
115184870Syongari	char	junkpath[PATH_MAX];
116184870Syongari	char	extpath[PATH_MAX];
117184870Syongari	char	subdir[PATH_MAX];
118184870Syongari	char	filename[PATH_MAX];
119184870Syongari	char	hostname[HOST_NAME_MAX+1];
120184870Syongari
121184870Syongari	char	tmp[PATH_MAX];
122184870Syongari	char	new[PATH_MAX];
123184870Syongari
124184870Syongari	int	ret;
125184870Syongari
126184870Syongari	int	fd;
127184870Syongari	FILE    *fp;
128184870Syongari	char	*line = NULL;
129184870Syongari	size_t	linesize = 0;
130184870Syongari	struct stat	sb;
131184870Syongari	char	*home;
132184870Syongari	char	*extension;
133184870Syongari
134184870Syongari	int	is_junk = 0;
135184870Syongari	int	in_hdr  = 1;
136184870Syongari
137184870Syongari	if (dirname == NULL) {
138184870Syongari		if ((home = getenv("HOME")) == NULL)
139184870Syongari			err(1, NULL);
140184870Syongari		ret = snprintf(rootpath, sizeof rootpath, "%s/Maildir", home);
141184870Syongari		if (ret < 0 || (size_t)ret >= sizeof rootpath)
142184870Syongari			errc(1, ENAMETOOLONG, "%s/Maildir", home);
143184870Syongari		dirname = rootpath;
144184870Syongari	}
145184870Syongari	maildir_mkdirs(dirname);
146184870Syongari
147184870Syongari	if (junk) {
148184870Syongari		/* create Junk subdirectory */
149184870Syongari		ret = snprintf(junkpath, sizeof junkpath, "%s/.Junk", dirname);
150184870Syongari		if (ret < 0 || (size_t)ret >= sizeof junkpath)
151184870Syongari			errc(1, ENAMETOOLONG, "%s/.Junk", dirname);
152184870Syongari		maildir_mkdirs(junkpath);
153184870Syongari	}
154184870Syongari
155184870Syongari	if ((extension = getenv("EXTENSION")) != NULL) {
156184870Syongari		if (maildir_subdir(extension, subdir, sizeof(subdir)) &&
157184870Syongari		    subdir[0]) {
158184870Syongari			ret = snprintf(extpath, sizeof extpath, "%s/.%s",
159184870Syongari			    dirname, subdir);
160184870Syongari			if (ret < 0 || (size_t)ret >= sizeof extpath)
161184870Syongari				errc(1, ENAMETOOLONG, "%s/.%s",
162184870Syongari				    dirname, subdir);
163184870Syongari			if (stat(extpath, &sb) != -1) {
164184870Syongari				dirname = extpath;
165184870Syongari				maildir_mkdirs(dirname);
166184870Syongari			}
167184870Syongari		}
168184870Syongari	}
169184870Syongari
170184870Syongari	if (gethostname(hostname, sizeof hostname) != 0)
171184870Syongari		(void)strlcpy(hostname, "localhost", sizeof hostname);
172184870Syongari
173184870Syongari	(void)snprintf(filename, sizeof filename, "%lld.%08x.%s",
174184870Syongari	    (long long)time(NULL),
175184870Syongari	    arc4random(),
176184870Syongari	    hostname);
177184870Syongari
178184870Syongari	(void)snprintf(tmp, sizeof tmp, "%s/tmp/%s", dirname, filename);
179184870Syongari
180184870Syongari	fd = open(tmp, O_CREAT | O_EXCL | O_WRONLY, 0600);
181184870Syongari	if (fd == -1)
182184870Syongari		err(EX_TEMPFAIL, NULL);
183184870Syongari	if ((fp = fdopen(fd, "w")) == NULL)
184184870Syongari		err(EX_TEMPFAIL, NULL);
185184870Syongari
186184870Syongari	while (getline(&line, &linesize, stdin) != -1) {
187184870Syongari		line[strcspn(line, "\n")] = '\0';
188184870Syongari		if (line[0] == '\0')
189184870Syongari			in_hdr = 0;
190184870Syongari		if (junk && in_hdr &&
191184870Syongari		    (strcasecmp(line, "x-spam: yes") == 0 ||
192184870Syongari			strcasecmp(line, "x-spam-flag: yes") == 0))
193184870Syongari			is_junk = 1;
194184870Syongari		fprintf(fp, "%s\n", line);
195184870Syongari	}
196184870Syongari	free(line);
197184870Syongari	if (ferror(stdin))
198184870Syongari		err(EX_TEMPFAIL, NULL);
199184870Syongari
200184870Syongari	if (fflush(fp) == EOF ||
201184870Syongari	    ferror(fp) ||
202184870Syongari	    fsync(fd) == -1 ||
203184870Syongari	    fclose(fp) == EOF)
204184870Syongari		err(EX_TEMPFAIL, NULL);
205184870Syongari
206184870Syongari	(void)snprintf(new, sizeof new, "%s/new/%s",
207184870Syongari	    is_junk ? junkpath : dirname, filename);
208184870Syongari
209184870Syongari	if (rename(tmp, new) == -1)
210184870Syongari		err(EX_TEMPFAIL, NULL);
211184870Syongari
212184870Syongari	exit(0);
213184870Syongari}
214184870Syongari
215184870Syongari
216184870Syongaristatic int
217184870Syongarimkdirs_component(const char *path, mode_t mode)
218184870Syongari{
219184870Syongari	struct stat	sb;
220184870Syongari
221184870Syongari	if (stat(path, &sb) == -1) {
222184870Syongari		if (errno != ENOENT)
223184870Syongari			return 0;
224184870Syongari		if (mkdir(path, mode | S_IWUSR | S_IXUSR) == -1)
225184870Syongari			return 0;
226184870Syongari	}
227184870Syongari	else if (!S_ISDIR(sb.st_mode)) {
228184870Syongari		errno = ENOTDIR;
229184870Syongari		return 0;
230184870Syongari	}
231184870Syongari
232184870Syongari	return 1;
233184870Syongari}
234184870Syongari
235184870Syongaristatic int
236184870Syongarimkdirs(const char *path, mode_t mode)
237184870Syongari{
238184870Syongari	char	 buf[PATH_MAX];
239184870Syongari	int	 i = 0;
240184870Syongari	int	 done = 0;
241184870Syongari	const char	*p;
242184870Syongari
243184870Syongari	/* absolute path required */
244184870Syongari	if (*path != '/') {
245184870Syongari		errno = EINVAL;
246184870Syongari		return 0;
247184870Syongari	}
248184870Syongari
249184870Syongari	/* make sure we don't exceed PATH_MAX */
250	if (strlen(path) >= sizeof buf) {
251		errno = ENAMETOOLONG;
252		return 0;
253	}
254
255	memset(buf, 0, sizeof buf);
256	for (p = path; *p; p++) {
257		if (*p == '/') {
258			if (buf[0] != '\0')
259				if (!mkdirs_component(buf, mode))
260					return 0;
261			while (*p == '/')
262				p++;
263			buf[i++] = '/';
264			buf[i++] = *p;
265			if (*p == '\0' && ++done)
266				break;
267			continue;
268		}
269		buf[i++] = *p;
270	}
271	if (!done)
272		if (!mkdirs_component(buf, mode))
273			return 0;
274
275	if (chmod(path, mode) == -1)
276		return 0;
277
278	return 1;
279}
280