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