diff options
Diffstat (limited to 'smtpd/mail.maildir.c')
-rw-r--r-- | smtpd/mail.maildir.c | 284 |
1 files changed, 284 insertions, 0 deletions
diff --git a/smtpd/mail.maildir.c b/smtpd/mail.maildir.c new file mode 100644 index 00000000..fe6adba6 --- /dev/null +++ b/smtpd/mail.maildir.c @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2017 Gilles Chehade <gilles@poolp.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#ifndef nitems +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#endif + +#include <sys/types.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <sysexits.h> +#include <unistd.h> + +#define MAILADDR_ESCAPE "!#$%&'*/?^`{|}~" + +static int maildir_subdir(const char *, char *, size_t); +static void maildir_mkdirs(const char *); +static void maildir_engine(const char *, int); +static int mkdirs_component(const char *, mode_t); +static int mkdirs(const char *, mode_t); + +int +main(int argc, char *argv[]) +{ + int ch; + int junk = 0; + + if (! geteuid()) + errx(1, "mail.maildir: may not be executed as root"); + + while ((ch = getopt(argc, argv, "j")) != -1) { + switch (ch) { + case 'j': + junk = 1; + break; + default: + break; + } + } + argc -= optind; + argv += optind; + + if (argc > 1) + errx(1, "mail.maildir: only one maildir is allowed"); + + maildir_engine(argv[0], junk); + + return (0); +} + +static int +maildir_subdir(const char *extension, char *dest, size_t len) +{ + char *sanitized; + + if (strlcpy(dest, extension, len) >= len) + return 0; + + for (sanitized = dest; *sanitized; sanitized++) + if (strchr(MAILADDR_ESCAPE, *sanitized)) + *sanitized = ':'; + + return 1; +} + +static void +maildir_mkdirs(const char *dirname) +{ + uint i; + int ret; + char pathname[PATH_MAX]; + char *subdirs[] = { "cur", "tmp", "new" }; + + if (mkdirs(dirname, 0700) == -1 && errno != EEXIST) { + if (errno == EINVAL || errno == ENAMETOOLONG) + err(1, NULL); + err(EX_TEMPFAIL, NULL); + } + + for (i = 0; i < nitems(subdirs); ++i) { + ret = snprintf(pathname, sizeof pathname, "%s/%s", dirname, + subdirs[i]); + if (ret < 0 || (size_t)ret >= sizeof pathname) + errc(1, ENAMETOOLONG, "%s/%s", dirname, subdirs[i]); + if (mkdir(pathname, 0700) == -1 && errno != EEXIST) + err(EX_TEMPFAIL, NULL); + } +} + +static void +maildir_engine(const char *dirname, int junk) +{ + char rootpath[PATH_MAX]; + char junkpath[PATH_MAX]; + char extpath[PATH_MAX]; + char subdir[PATH_MAX]; + char filename[PATH_MAX]; + char hostname[HOST_NAME_MAX+1]; + + char tmp[PATH_MAX]; + char new[PATH_MAX]; + + int ret; + + int fd; + FILE *fp; + char *line = NULL; + size_t linesize = 0; + ssize_t linelen; + struct stat sb; + char *home; + char *extension; + + int is_junk = 0; + int in_hdr = 1; + + if (dirname == NULL) { + if ((home = getenv("HOME")) == NULL) + err(1, NULL); + ret = snprintf(rootpath, sizeof rootpath, "%s/Maildir", home); + if (ret < 0 || (size_t)ret >= sizeof rootpath) + errc(1, ENAMETOOLONG, "%s/Maildir", home); + dirname = rootpath; + } + maildir_mkdirs(dirname); + + if (junk) { + /* create Junk subdirectory */ + ret = snprintf(junkpath, sizeof junkpath, "%s/.Junk", dirname); + if (ret < 0 || (size_t)ret >= sizeof junkpath) + errc(1, ENAMETOOLONG, "%s/.Junk", dirname); + maildir_mkdirs(junkpath); + } + + if ((extension = getenv("EXTENSION")) != NULL) { + if (maildir_subdir(extension, subdir, sizeof(subdir)) && + subdir[0]) { + ret = snprintf(extpath, sizeof extpath, "%s/.%s", + dirname, subdir); + if (ret < 0 || (size_t)ret >= sizeof extpath) + errc(1, ENAMETOOLONG, "%s/.%s", + dirname, subdir); + if (stat(extpath, &sb) != -1) { + dirname = extpath; + maildir_mkdirs(dirname); + } + } + } + + if (gethostname(hostname, sizeof hostname) != 0) + (void)strlcpy(hostname, "localhost", sizeof hostname); + + (void)snprintf(filename, sizeof filename, "%lld.%08x.%s", + (long long int) time(NULL), + arc4random(), + hostname); + + (void)snprintf(tmp, sizeof tmp, "%s/tmp/%s", dirname, filename); + + fd = open(tmp, O_CREAT | O_EXCL | O_WRONLY, 0600); + if (fd == -1) + err(EX_TEMPFAIL, NULL); + if ((fp = fdopen(fd, "w")) == NULL) + err(EX_TEMPFAIL, NULL); + + while ((linelen = getline(&line, &linesize, stdin)) != -1) { + line[strcspn(line, "\n")] = '\0'; + if (line[0] == '\0') + in_hdr = 0; + if (junk && in_hdr && + (strcasecmp(line, "x-spam: yes") == 0 || + strcasecmp(line, "x-spam-flag: yes") == 0)) + is_junk = 1; + fprintf(fp, "%s\n", line); + } + free(line); + if (ferror(stdin)) + err(EX_TEMPFAIL, NULL); + + if (fflush(fp) == EOF || + ferror(fp) || + fsync(fd) == -1 || + fclose(fp) == EOF) + err(EX_TEMPFAIL, NULL); + + (void)snprintf(new, sizeof new, "%s/new/%s", + is_junk ? junkpath : dirname, filename); + + if (rename(tmp, new) == -1) + err(EX_TEMPFAIL, NULL); + + exit(0); +} + + +static int +mkdirs_component(const char *path, mode_t mode) +{ + struct stat sb; + + if (stat(path, &sb) == -1) { + if (errno != ENOENT) + return 0; + if (mkdir(path, mode | S_IWUSR | S_IXUSR) == -1) + return 0; + } + else if (!S_ISDIR(sb.st_mode)) { + errno = ENOTDIR; + return 0; + } + + return 1; +} + +static int +mkdirs(const char *path, mode_t mode) +{ + char buf[PATH_MAX]; + int i = 0; + int done = 0; + const char *p; + + /* absolute path required */ + if (*path != '/') { + errno = EINVAL; + return 0; + } + + /* make sure we don't exceed PATH_MAX */ + if (strlen(path) >= sizeof buf) { + errno = ENAMETOOLONG; + return 0; + } + + memset(buf, 0, sizeof buf); + for (p = path; *p; p++) { + if (*p == '/') { + if (buf[0] != '\0') + if (!mkdirs_component(buf, mode)) + return 0; + while (*p == '/') + p++; + buf[i++] = '/'; + buf[i++] = *p; + if (*p == '\0' && ++done) + break; + continue; + } + buf[i++] = *p; + } + if (!done) + if (!mkdirs_component(buf, mode)) + return 0; + + if (chmod(path, mode) == -1) + return 0; + + return 1; +} |