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/types.h> 22 #include <sys/stat.h> 23 24 #include <ctype.h> 25 #include <err.h> 26 #include <errno.h> 27 #include <fcntl.h> 28 #include <limits.h> 29 #include <netdb.h> 30 #include <stdio.h> 31 #include <stdlib.h> 32 #include <string.h> 33 #include <time.h> 34 #include <sysexits.h> 35 #include <unistd.h> 36 37 #define MAILADDR_ESCAPE "!#$%&'*/?^`{|}~" 38 39 static int maildir_subdir(const char *, char *, size_t); 40 static void maildir_mkdirs(const char *); 41 static void maildir_engine(const char *, int); 42 static int mkdirs_component(const char *, mode_t); 43 static int mkdirs(const char *, mode_t); 44 45 int 46 main(int argc, char *argv[]) 47 { 48 int ch; 49 int junk = 0; 50 51 if (! geteuid()) 52 errx(1, "mail.maildir: may not be executed as root"); 53 54 while ((ch = getopt(argc, argv, "j")) != -1) { 55 switch (ch) { 56 case 'j': 57 junk = 1; 58 break; 59 default: 60 break; 61 } 62 } 63 argc -= optind; 64 argv += optind; 65 66 if (argc > 1) 67 errx(1, "mail.maildir: only one maildir is allowed"); 68 69 maildir_engine(argv[0], junk); 70 71 return (0); 72 } 73 74 static int 75 maildir_subdir(const char *extension, char *dest, size_t len) 76 { 77 char *sanitized; 78 79 if (strlcpy(dest, extension, len) >= len) 80 return 0; 81 82 for (sanitized = dest; *sanitized; sanitized++) 83 if (strchr(MAILADDR_ESCAPE, *sanitized)) 84 *sanitized = ':'; 85 86 return 1; 87 } 88 89 static void 90 maildir_mkdirs(const char *dirname) 91 { 92 uint i; 93 int ret; 94 char pathname[PATH_MAX]; 95 char *subdirs[] = { "cur", "tmp", "new" }; 96 97 if (mkdirs(dirname, 0700) == -1 && errno != EEXIST) { 98 if (errno == EINVAL || errno == ENAMETOOLONG) 99 err(1, NULL); 100 err(EX_TEMPFAIL, NULL); 101 } 102 103 for (i = 0; i < nitems(subdirs); ++i) { 104 ret = snprintf(pathname, sizeof pathname, "%s/%s", dirname, 105 subdirs[i]); 106 if (ret < 0 || (size_t)ret >= sizeof pathname) 107 errc(1, ENAMETOOLONG, "%s/%s", dirname, subdirs[i]); 108 if (mkdir(pathname, 0700) == -1 && errno != EEXIST) 109 err(EX_TEMPFAIL, NULL); 110 } 111 } 112 113 static void 114 maildir_engine(const char *dirname, int junk) 115 { 116 char rootpath[PATH_MAX]; 117 char junkpath[PATH_MAX]; 118 char extpath[PATH_MAX]; 119 char subdir[PATH_MAX]; 120 char filename[PATH_MAX]; 121 char hostname[HOST_NAME_MAX+1]; 122 123 char tmp[PATH_MAX]; 124 char new[PATH_MAX]; 125 126 int ret; 127 128 int fd; 129 FILE *fp; 130 char *line = NULL; 131 size_t linesize = 0; 132 struct stat sb; 133 char *home; 134 char *extension; 135 136 int is_junk = 0; 137 int in_hdr = 1; 138 139 if (dirname == NULL) { 140 if ((home = getenv("HOME")) == NULL) 141 err(1, NULL); 142 ret = snprintf(rootpath, sizeof rootpath, "%s/Maildir", home); 143 if (ret < 0 || (size_t)ret >= sizeof rootpath) 144 errc(1, ENAMETOOLONG, "%s/Maildir", home); 145 dirname = rootpath; 146 } 147 maildir_mkdirs(dirname); 148 149 if (junk) { 150 /* create Junk subdirectory */ 151 ret = snprintf(junkpath, sizeof junkpath, "%s/.Junk", dirname); 152 if (ret < 0 || (size_t)ret >= sizeof junkpath) 153 errc(1, ENAMETOOLONG, "%s/.Junk", dirname); 154 maildir_mkdirs(junkpath); 155 } 156 157 if ((extension = getenv("EXTENSION")) != NULL) { 158 if (maildir_subdir(extension, subdir, sizeof(subdir)) && 159 subdir[0]) { 160 ret = snprintf(extpath, sizeof extpath, "%s/.%s", 161 dirname, subdir); 162 if (ret < 0 || (size_t)ret >= sizeof extpath) 163 errc(1, ENAMETOOLONG, "%s/.%s", 164 dirname, subdir); 165 if (stat(extpath, &sb) != -1) { 166 dirname = extpath; 167 maildir_mkdirs(dirname); 168 } 169 } 170 } 171 172 if (gethostname(hostname, sizeof hostname) != 0) 173 (void)strlcpy(hostname, "localhost", sizeof hostname); 174 175 (void)snprintf(filename, sizeof filename, "%lld.%08x.%s", 176 (long long int) time(NULL), 177 arc4random(), 178 hostname); 179 180 (void)snprintf(tmp, sizeof tmp, "%s/tmp/%s", dirname, filename); 181 182 fd = open(tmp, O_CREAT | O_EXCL | O_WRONLY, 0600); 183 if (fd == -1) 184 err(EX_TEMPFAIL, NULL); 185 if ((fp = fdopen(fd, "w")) == NULL) 186 err(EX_TEMPFAIL, NULL); 187 188 while (getline(&line, &linesize, stdin) != -1) { 189 line[strcspn(line, "\n")] = '\0'; 190 if (line[0] == '\0') 191 in_hdr = 0; 192 if (junk && in_hdr && 193 (strcasecmp(line, "x-spam: yes") == 0 || 194 strcasecmp(line, "x-spam-flag: yes") == 0)) 195 is_junk = 1; 196 fprintf(fp, "%s\n", line); 197 } 198 free(line); 199 if (ferror(stdin)) 200 err(EX_TEMPFAIL, NULL); 201 202 if (fflush(fp) == EOF || 203 ferror(fp) || 204 fsync(fd) == -1 || 205 fclose(fp) == EOF) 206 err(EX_TEMPFAIL, NULL); 207 208 (void)snprintf(new, sizeof new, "%s/new/%s", 209 is_junk ? junkpath : dirname, filename); 210 211 if (rename(tmp, new) == -1) 212 err(EX_TEMPFAIL, NULL); 213 214 exit(0); 215 } 216 217 218 static int 219 mkdirs_component(const char *path, mode_t mode) 220 { 221 struct stat sb; 222 223 if (stat(path, &sb) == -1) { 224 if (errno != ENOENT) 225 return 0; 226 if (mkdir(path, mode | S_IWUSR | S_IXUSR) == -1) 227 return 0; 228 } 229 else if (!S_ISDIR(sb.st_mode)) { 230 errno = ENOTDIR; 231 return 0; 232 } 233 234 return 1; 235 } 236 237 static int 238 mkdirs(const char *path, mode_t mode) 239 { 240 char buf[PATH_MAX]; 241 int i = 0; 242 int done = 0; 243 const char *p; 244 245 /* absolute path required */ 246 if (*path != '/') { 247 errno = EINVAL; 248 return 0; 249 } 250 251 /* make sure we don't exceed PATH_MAX */ 252 if (strlen(path) >= sizeof buf) { 253 errno = ENAMETOOLONG; 254 return 0; 255 } 256 257 memset(buf, 0, sizeof buf); 258 for (p = path; *p; p++) { 259 if (*p == '/') { 260 if (buf[0] != '\0') 261 if (!mkdirs_component(buf, mode)) 262 return 0; 263 while (*p == '/') 264 p++; 265 buf[i++] = '/'; 266 buf[i++] = *p; 267 if (*p == '\0' && ++done) 268 break; 269 continue; 270 } 271 buf[i++] = *p; 272 } 273 if (!done) 274 if (!mkdirs_component(buf, mode)) 275 return 0; 276 277 if (chmod(path, mode) == -1) 278 return 0; 279 280 return 1; 281 } 282