1 /*
2 * Copyright (c) 2017 Gilles Chehade <gilles@poolp.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #ifndef nitems
18 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
19 #endif
20
21 #include <sys/stat.h>
22
23 #include <err.h>
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <limits.h>
27 #include <netdb.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <sysexits.h>
32 #include <time.h>
33 #include <unistd.h>
34
35 #define MAILADDR_ESCAPE "!#$%&'*/?^`{|}~"
36
37 static int maildir_subdir(const char *, char *, size_t);
38 static void maildir_mkdirs(const char *);
39 static void maildir_engine(const char *, int);
40 static int mkdirs_component(const char *, mode_t);
41 static int mkdirs(const char *, mode_t);
42
43 int
main(int argc,char * argv[])44 main(int argc, char *argv[])
45 {
46 int ch;
47 int junk = 0;
48
49 if (! geteuid())
50 errx(1, "mail.maildir: may not be executed as root");
51
52 while ((ch = getopt(argc, argv, "j")) != -1) {
53 switch (ch) {
54 case 'j':
55 junk = 1;
56 break;
57 default:
58 break;
59 }
60 }
61 argc -= optind;
62 argv += optind;
63
64 if (argc > 1)
65 errx(1, "mail.maildir: only one maildir is allowed");
66
67 maildir_engine(argv[0], junk);
68
69 return (0);
70 }
71
72 static int
maildir_subdir(const char * extension,char * dest,size_t len)73 maildir_subdir(const char *extension, char *dest, size_t len)
74 {
75 char *sanitized;
76
77 if (strlcpy(dest, extension, len) >= len)
78 return 0;
79
80 for (sanitized = dest; *sanitized; sanitized++)
81 if (strchr(MAILADDR_ESCAPE, *sanitized))
82 *sanitized = ':';
83
84 return 1;
85 }
86
87 static void
maildir_mkdirs(const char * dirname)88 maildir_mkdirs(const char *dirname)
89 {
90 uint i;
91 int ret;
92 char pathname[PATH_MAX];
93 char *subdirs[] = { "cur", "tmp", "new" };
94
95 if (mkdirs(dirname, 0700) == -1 && errno != EEXIST) {
96 if (errno == EINVAL || errno == ENAMETOOLONG)
97 err(1, NULL);
98 err(EX_TEMPFAIL, NULL);
99 }
100
101 for (i = 0; i < nitems(subdirs); ++i) {
102 ret = snprintf(pathname, sizeof pathname, "%s/%s", dirname,
103 subdirs[i]);
104 if (ret < 0 || (size_t)ret >= sizeof pathname)
105 errc(1, ENAMETOOLONG, "%s/%s", dirname, subdirs[i]);
106 if (mkdir(pathname, 0700) == -1 && errno != EEXIST)
107 err(EX_TEMPFAIL, NULL);
108 }
109 }
110
111 static void
maildir_engine(const char * dirname,int junk)112 maildir_engine(const char *dirname, int junk)
113 {
114 char rootpath[PATH_MAX];
115 char junkpath[PATH_MAX];
116 char extpath[PATH_MAX];
117 char subdir[PATH_MAX];
118 char filename[PATH_MAX];
119 char hostname[HOST_NAME_MAX+1];
120
121 char tmp[PATH_MAX];
122 char new[PATH_MAX];
123
124 int ret;
125
126 int fd;
127 FILE *fp;
128 char *line = NULL;
129 size_t linesize = 0;
130 struct stat sb;
131 char *home;
132 char *extension;
133
134 int is_junk = 0;
135 int in_hdr = 1;
136
137 if (dirname == NULL) {
138 if ((home = getenv("HOME")) == NULL)
139 err(1, NULL);
140 ret = snprintf(rootpath, sizeof rootpath, "%s/Maildir", home);
141 if (ret < 0 || (size_t)ret >= sizeof rootpath)
142 errc(1, ENAMETOOLONG, "%s/Maildir", home);
143 dirname = rootpath;
144 }
145 maildir_mkdirs(dirname);
146
147 if (junk) {
148 /* create Junk subdirectory */
149 ret = snprintf(junkpath, sizeof junkpath, "%s/.Junk", dirname);
150 if (ret < 0 || (size_t)ret >= sizeof junkpath)
151 errc(1, ENAMETOOLONG, "%s/.Junk", dirname);
152 maildir_mkdirs(junkpath);
153 }
154
155 if ((extension = getenv("EXTENSION")) != NULL) {
156 if (maildir_subdir(extension, subdir, sizeof(subdir)) &&
157 subdir[0]) {
158 ret = snprintf(extpath, sizeof extpath, "%s/.%s",
159 dirname, subdir);
160 if (ret < 0 || (size_t)ret >= sizeof extpath)
161 errc(1, ENAMETOOLONG, "%s/.%s",
162 dirname, subdir);
163 if (stat(extpath, &sb) != -1) {
164 dirname = extpath;
165 maildir_mkdirs(dirname);
166 }
167 }
168 }
169
170 if (gethostname(hostname, sizeof hostname) != 0)
171 (void)strlcpy(hostname, "localhost", sizeof hostname);
172
173 (void)snprintf(filename, sizeof filename, "%lld.%08x.%s",
174 (long long)time(NULL),
175 arc4random(),
176 hostname);
177
178 (void)snprintf(tmp, sizeof tmp, "%s/tmp/%s", dirname, filename);
179
180 fd = open(tmp, O_CREAT | O_EXCL | O_WRONLY, 0600);
181 if (fd == -1)
182 err(EX_TEMPFAIL, NULL);
183 if ((fp = fdopen(fd, "w")) == NULL)
184 err(EX_TEMPFAIL, NULL);
185
186 while (getline(&line, &linesize, stdin) != -1) {
187 line[strcspn(line, "\n")] = '\0';
188 if (line[0] == '\0')
189 in_hdr = 0;
190 if (junk && in_hdr &&
191 (strcasecmp(line, "x-spam: yes") == 0 ||
192 strcasecmp(line, "x-spam-flag: yes") == 0))
193 is_junk = 1;
194 fprintf(fp, "%s\n", line);
195 }
196 free(line);
197 if (ferror(stdin))
198 err(EX_TEMPFAIL, NULL);
199
200 if (fflush(fp) == EOF ||
201 ferror(fp) ||
202 fsync(fd) == -1 ||
203 fclose(fp) == EOF)
204 err(EX_TEMPFAIL, NULL);
205
206 (void)snprintf(new, sizeof new, "%s/new/%s",
207 is_junk ? junkpath : dirname, filename);
208
209 if (rename(tmp, new) == -1)
210 err(EX_TEMPFAIL, NULL);
211
212 exit(0);
213 }
214
215
216 static int
mkdirs_component(const char * path,mode_t mode)217 mkdirs_component(const char *path, mode_t mode)
218 {
219 struct stat sb;
220
221 if (stat(path, &sb) == -1) {
222 if (errno != ENOENT)
223 return 0;
224 if (mkdir(path, mode | S_IWUSR | S_IXUSR) == -1)
225 return 0;
226 }
227 else if (!S_ISDIR(sb.st_mode)) {
228 errno = ENOTDIR;
229 return 0;
230 }
231
232 return 1;
233 }
234
235 static int
mkdirs(const char * path,mode_t mode)236 mkdirs(const char *path, mode_t mode)
237 {
238 char buf[PATH_MAX];
239 int i = 0;
240 int done = 0;
241 const char *p;
242
243 /* absolute path required */
244 if (*path != '/') {
245 errno = EINVAL;
246 return 0;
247 }
248
249 /* 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