diff options
Diffstat (limited to 'usr.sbin/smtpd/smtpd.c')
-rw-r--r-- | usr.sbin/smtpd/smtpd.c | 402 |
1 files changed, 127 insertions, 275 deletions
diff --git a/usr.sbin/smtpd/smtpd.c b/usr.sbin/smtpd/smtpd.c index b0f6a2ec08b..1119dabc9d4 100644 --- a/usr.sbin/smtpd/smtpd.c +++ b/usr.sbin/smtpd/smtpd.c @@ -1,9 +1,9 @@ -/* $OpenBSD: smtpd.c,v 1.108 2010/05/31 22:25:26 chl Exp $ */ +/* $OpenBSD: smtpd.c,v 1.109 2010/05/31 23:38:56 jacekm Exp $ */ /* * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> - * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net> + * Copyright (c) 2009-2010 Jacek Masiulaniec <jacekm@dobremiasto.net> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -28,6 +28,7 @@ #include <sys/uio.h> #include <sys/mman.h> +#include <dirent.h> #include <err.h> #include <errno.h> #include <event.h> @@ -45,6 +46,7 @@ #include <unistd.h> #include "smtpd.h" +#include "queue_backend.h" void parent_imsg(struct smtpd *, struct imsgev *, struct imsg *); __dead void usage(void); @@ -57,10 +59,8 @@ void parent_sig_handler(int, short, void *); void forkmda(struct smtpd *, struct imsgev *, u_int32_t, struct deliver *); -int parent_enqueue_offline(struct smtpd *, char *); +void parent_enqueue_offline(struct smtpd *); int parent_forward_open(char *); -int setup_spool(uid_t, gid_t); -int path_starts_with(char *, char *); void fork_peers(struct smtpd *); @@ -68,11 +68,11 @@ struct child *child_add(struct smtpd *, pid_t, int, int); void child_del(struct smtpd *, pid_t); struct child *child_lookup(struct smtpd *, pid_t); +void setup_spool(struct passwd *); + extern char **environ; void (*imsg_callback)(struct smtpd *, struct imsgev *, struct imsg *); -int __b64_pton(char const *, unsigned char *, size_t); - void parent_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) { @@ -116,17 +116,6 @@ parent_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) } } - if (iev->proc == PROC_QUEUE) { - switch (imsg->hdr.type) { - case IMSG_PARENT_ENQUEUE_OFFLINE: - if (! parent_enqueue_offline(env, imsg->data)) - imsg_compose_event(iev, - IMSG_PARENT_ENQUEUE_OFFLINE, 0, 0, -1, - NULL, 0); - return; - } - } - if (iev->proc == PROC_MDA) { switch (imsg->hdr.type) { case IMSG_PARENT_FORK_MDA: @@ -180,6 +169,7 @@ parent_imsg(struct smtpd *env, struct imsgev *iev, struct imsg *imsg) } } + log_warnx("parent got imsg %d from %d", imsg->hdr.type, iev->proc); fatalx("parent_imsg: unexpected imsg"); } @@ -384,12 +374,10 @@ parent_sig_handler(int sig, short event, void *p) case CHILD_ENQUEUE_OFFLINE: if (fail) log_warnx("couldn't enqueue offline " - "message; smtpctl %s", cause); + "message; child %s", cause); else log_debug("offline message enqueued"); - imsg_compose_event(env->sc_ievs[PROC_QUEUE], - IMSG_PARENT_ENQUEUE_OFFLINE, 0, 0, -1, - NULL, 0); + parent_enqueue_offline(env); break; default: @@ -483,12 +471,11 @@ main(int argc, char *argv[]) if (geteuid()) errx(1, "need root privileges"); - if ((env.sc_pw = getpwnam(SMTPD_USER)) == NULL) + if ((env.sc_pw = getpwnam(SMTPD_USER)) == NULL) errx(1, "unknown user %s", SMTPD_USER); - if (!setup_spool(env.sc_pw->pw_uid, 0)) - errx(1, "invalid directory permissions"); - + setup_spool(env.sc_pw); + log_init(debug); log_verbose(verbose); @@ -531,6 +518,8 @@ main(int argc, char *argv[]) bzero(&tv, sizeof(tv)); evtimer_add(&env.sc_ev, &tv); + parent_enqueue_offline(&env); + event_dispatch(); return (0); @@ -563,7 +552,6 @@ fork_peers(struct smtpd *env) env->sc_instances[PROC_MTA] = 1; env->sc_instances[PROC_PARENT] = 1; env->sc_instances[PROC_QUEUE] = 1; - env->sc_instances[PROC_RUNNER] = 1; env->sc_instances[PROC_SMTP] = 1; init_pipes(env); @@ -574,7 +562,6 @@ fork_peers(struct smtpd *env) env->sc_title[PROC_MFA] = "mail filter agent"; env->sc_title[PROC_MTA] = "mail transfer agent"; env->sc_title[PROC_QUEUE] = "queue"; - env->sc_title[PROC_RUNNER] = "runner"; env->sc_title[PROC_SMTP] = "smtp server"; child_add(env, control(env), CHILD_DAEMON, PROC_CONTROL); @@ -583,7 +570,6 @@ fork_peers(struct smtpd *env) child_add(env, mfa(env), CHILD_DAEMON, PROC_MFA); child_add(env, mta(env), CHILD_DAEMON, PROC_MTA); child_add(env, queue(env), CHILD_DAEMON, PROC_QUEUE); - child_add(env, runner(env), CHILD_DAEMON, PROC_RUNNER); child_add(env, smtp(env), CHILD_DAEMON, PROC_SMTP); setproctitle("[priv]"); @@ -630,136 +616,6 @@ child_lookup(struct smtpd *env, pid_t pid) return SPLAY_FIND(childtree, &env->children, &key); } -int -setup_spool(uid_t uid, gid_t gid) -{ - unsigned int n; - char *paths[] = { PATH_INCOMING, PATH_ENQUEUE, PATH_QUEUE, - PATH_RUNQUEUE, PATH_PURGE, - PATH_OFFLINE, PATH_BOUNCE }; - char pathname[MAXPATHLEN]; - struct stat sb; - int ret; - - if (! bsnprintf(pathname, sizeof(pathname), "%s", PATH_SPOOL)) - fatal("snprintf"); - - if (stat(pathname, &sb) == -1) { - if (errno != ENOENT) { - warn("stat: %s", pathname); - return 0; - } - - if (mkdir(pathname, 0711) == -1) { - warn("mkdir: %s", pathname); - return 0; - } - - if (chown(pathname, 0, 0) == -1) { - warn("chown: %s", pathname); - return 0; - } - - if (stat(pathname, &sb) == -1) - err(1, "stat: %s", pathname); - } - - /* check if it's a directory */ - if (!S_ISDIR(sb.st_mode)) { - warnx("%s is not a directory", pathname); - return 0; - } - - /* check that it is owned by uid/gid */ - if (sb.st_uid != 0 || sb.st_gid != 0) { - warnx("%s must be owned by root:wheel", pathname); - return 0; - } - - /* check permission */ - if ((sb.st_mode & (S_IRUSR|S_IWUSR|S_IXUSR)) != (S_IRUSR|S_IWUSR|S_IXUSR) || - (sb.st_mode & (S_IRGRP|S_IWGRP|S_IXGRP)) != S_IXGRP || - (sb.st_mode & (S_IROTH|S_IWOTH|S_IXOTH)) != S_IXOTH) { - warnx("%s must be rwx--x--x (0711)", pathname); - return 0; - } - - ret = 1; - for (n = 0; n < nitems(paths); n++) { - mode_t mode; - uid_t owner; - gid_t group; - - if (!strcmp(paths[n], PATH_OFFLINE)) { - mode = 01777; - owner = 0; - group = 0; - } else { - mode = 0700; - owner = uid; - group = gid; - } - - if (! bsnprintf(pathname, sizeof(pathname), "%s%s", PATH_SPOOL, - paths[n])) - fatal("snprintf"); - - if (stat(pathname, &sb) == -1) { - if (errno != ENOENT) { - warn("stat: %s", pathname); - ret = 0; - continue; - } - - /* chmod is deffered to avoid umask effect */ - if (mkdir(pathname, 0) == -1) { - ret = 0; - warn("mkdir: %s", pathname); - } - - if (chown(pathname, owner, group) == -1) { - ret = 0; - warn("chown: %s", pathname); - } - - if (chmod(pathname, mode) == -1) { - ret = 0; - warn("chmod: %s", pathname); - } - - if (stat(pathname, &sb) == -1) - err(1, "stat: %s", pathname); - } - - /* check if it's a directory */ - if (!S_ISDIR(sb.st_mode)) { - ret = 0; - warnx("%s is not a directory", pathname); - } - - /* check that it is owned by owner/group */ - if (sb.st_uid != owner) { - ret = 0; - warnx("%s is not owned by uid %d", pathname, owner); - } - if (sb.st_gid != group) { - ret = 0; - warnx("%s is not owned by gid %d", pathname, group); - } - - /* check permission */ - if ((sb.st_mode & 07777) != mode) { - char mode_str[12]; - - ret = 0; - strmode(mode, mode_str); - mode_str[10] = '\0'; - warnx("%s must be %s (%o)", pathname, mode_str + 1, mode); - } - } - return ret; -} - void imsg_event_add(struct imsgev *iev) { @@ -802,8 +658,11 @@ forkmda(struct smtpd *env, struct imsgev *iev, u_int32_t id, errno = 0; pw = getpwnam(deliver->user); if (pw == NULL) { - n = snprintf(ebuf, sizeof ebuf, "getpwnam: %s", - errno ? strerror(errno) : "no such user"); + if (errno) + n = snprintf(ebuf, sizeof ebuf, "getpwnam: %s", + strerror(errno)); + else + n = snprintf(ebuf, sizeof ebuf, "user not found"); imsg_compose_event(iev, IMSG_MDA_DONE, id, 0, -1, ebuf, n + 1); return; } @@ -886,7 +745,8 @@ forkmda(struct smtpd *env, struct imsgev *iev, u_int32_t id, /* avoid hangs by setting 5m timeout */ alarm(300); - if (deliver->mode == A_EXT) { + /* external mda */ + if (deliver->mode == 'P') { char *environ_new[2]; environ_new[0] = "PATH=" _PATH_DEFPATH; @@ -897,7 +757,8 @@ forkmda(struct smtpd *env, struct imsgev *iev, u_int32_t id, error("execle"); } - if (deliver->mode == A_MAILDIR) { + /* internal mda: maildir */ + if (deliver->mode == 'D') { char tmp[PATH_MAX], new[PATH_MAX]; int ch, fd; FILE *fp; @@ -927,10 +788,6 @@ forkmda(struct smtpd *env, struct imsgev *iev, u_int32_t id, break; if (ferror(stdin)) error2("read error"); - if (fflush(fp) == EOF || ferror(fp)) - error2("write error"); - if (fsync(fd) < 0) - error2("fsync"); if (fclose(fp) == EOF) error2("fclose"); snprintf(new, sizeof new, "new/%s", tmp + 4); @@ -940,7 +797,8 @@ forkmda(struct smtpd *env, struct imsgev *iev, u_int32_t id, } #undef error2 - if (deliver->mode == A_FILENAME) { + /* internal mda: file */ + if (deliver->mode == 'F') { struct stat sb; time_t now; size_t len; @@ -975,10 +833,6 @@ forkmda(struct smtpd *env, struct imsgev *iev, u_int32_t id, if (ferror(stdin)) error2("read error"); putc('\n', fp); - if (fflush(fp) == EOF || ferror(fp)) - error2("write error"); - if (fsync(fd) < 0) - error2("fsync"); if (fclose(fp) == EOF) error2("fclose"); _exit(0); @@ -989,114 +843,103 @@ forkmda(struct smtpd *env, struct imsgev *iev, u_int32_t id, #undef error #undef error2 -int -parent_enqueue_offline(struct smtpd *env, char *runner_path) +void +parent_enqueue_offline(struct smtpd *env) { - char path[MAXPATHLEN]; - struct passwd *pw; + char path[MAXPATHLEN], *line, charstr[2], *envp[2], *tmp; struct stat sb; + struct dirent *de; + struct passwd *pw; + DIR *dir; pid_t pid; + arglist args; + int fd, line_sz; - log_debug("parent_enqueue_offline: path %s", runner_path); - - if (! bsnprintf(path, sizeof(path), "%s%s", PATH_SPOOL, runner_path)) - fatalx("parent_enqueue_offline: filename too long"); + fd = -1; + pw = NULL; - if (! path_starts_with(path, PATH_SPOOL PATH_OFFLINE)) - fatalx("parent_enqueue_offline: path outside offline dir"); + dir = opendir(PATH_SPOOL PATH_OFFLINE); + if (dir == NULL) + fatal("opendir"); - if (lstat(path, &sb) == -1) { - if (errno == ENOENT) { - log_warn("parent_enqueue_offline: %s", path); - return (0); - } - fatal("parent_enqueue_offline: lstat"); - } - - if (chflags(path, 0) == -1) { - if (errno == ENOENT) { - log_warn("parent_enqueue_offline: %s", path); - return (0); + while ((de = readdir(dir))) { + if (de->d_name[0] == '.') + continue; + snprintf(path, sizeof path, "%s%s/%s", PATH_SPOOL, + PATH_OFFLINE, de->d_name); + log_debug("%s: file %s", __func__, path); + fd = open(path, O_RDONLY); + if (fd < 0) + continue; + if (fchflags(fd, 0) < 0 || fstat(fd, &sb) < 0 || + unlink(path) < 0 || !S_ISREG(sb.st_mode) || + (pw = getpwuid(sb.st_uid)) == NULL) { + close(fd); + continue; } - fatal("parent_enqueue_offline: chflags"); - } - - errno = 0; - if ((pw = getpwuid(sb.st_uid)) == NULL) { - log_warn("parent_enqueue_offline: getpwuid for uid %d failed", - sb.st_uid); - unlink(path); - return (0); - } - - if (! S_ISREG(sb.st_mode)) { - log_warnx("file %s (uid %d) not regular, removing", path, sb.st_uid); - if (S_ISDIR(sb.st_mode)) - rmdir(path); - else - unlink(path); - return (0); + break; } - if ((pid = fork()) == -1) - fatal("parent_enqueue_offline: fork"); - - if (pid == 0) { - char *envp[2], *p, *tmp; - FILE *fp; - size_t len; - arglist args; - - bzero(&args, sizeof(args)); - - if (setgroups(1, &pw->pw_gid) || - setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || - setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) || - closefrom(STDERR_FILENO + 1) == -1) { - unlink(path); - _exit(1); - } + closedir(dir); - if ((fp = fopen(path, "r")) == NULL) { - unlink(path); - _exit(1); - } - unlink(path); + if (de == NULL) + return; - if (chdir(pw->pw_dir) == -1 && chdir("/") == -1) - _exit(1); + pid = fork(); + if (pid < 0) + fatal("fork"); - if (setsid() == -1 || - signal(SIGPIPE, SIG_DFL) == SIG_ERR || - dup2(fileno(fp), STDIN_FILENO) == -1) - _exit(1); + if (pid) { + child_add(env, pid, CHILD_ENQUEUE_OFFLINE, -1); + return; + } - if ((p = fgetln(fp, &len)) == NULL) - _exit(1); + if (chdir(pw->pw_dir) < 0 && chdir("/") < 0) + _exit(1); + if (dup2(fd, STDIN_FILENO) < 0) + _exit(1); + if (closefrom(STDERR_FILENO + 1) < 0) + _exit(1); + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + _exit(1); + if (setsid() < 0) + _exit(1); + if (signal(SIGPIPE, SIG_DFL) == SIG_ERR || + signal(SIGINT, SIG_DFL) == SIG_ERR || + signal(SIGTERM, SIG_DFL) == SIG_ERR || + signal(SIGCHLD, SIG_DFL) == SIG_ERR || + signal(SIGHUP, SIG_DFL) == SIG_ERR) + _exit(1); - if (p[len - 1] != '\n') + line = malloc(1); + if (line == NULL) + _exit(1); + line_sz = 1; + line[0] = '\0'; + do { + if (read(STDIN_FILENO, charstr, 1) <= 0) _exit(1); - p[len - 1] = '\0'; - - addargs(&args, "%s", "sendmail"); - - while ((tmp = strsep(&p, "|")) != NULL) - addargs(&args, "%s", tmp); - - if (lseek(fileno(fp), len, SEEK_SET) == -1) + charstr[1] = '\0'; + line = realloc(line, ++line_sz); + if (line == NULL) _exit(1); - - envp[0] = "PATH=" _PATH_DEFPATH; - envp[1] = (char *)NULL; - environ = envp; - - execvp(PATH_SMTPCTL, args.list); - _exit(1); + strlcat(line, charstr, line_sz); + } while (charstr[0] != '\n'); + line[strcspn(line, "\n")] = '\0'; + + bzero(&args, sizeof args); + addargs(&args, "%s", "sendmail"); + while ((tmp = strsep(&line, "|"))) { + log_debug("%s: arg %s", __func__, tmp); + addargs(&args, "%s", tmp); } - - child_add(env, pid, CHILD_ENQUEUE_OFFLINE, -1); - - return (1); + envp[0] = "PATH=" _PATH_DEFPATH; + envp[1] = (char *)NULL; + environ = envp; + execvp(PATH_SMTPCTL, args.list); + _exit(1); } int @@ -1131,18 +974,6 @@ parent_forward_open(char *username) } int -path_starts_with(char *file, char *prefix) -{ - char rprefix[MAXPATHLEN]; - char rfile[MAXPATHLEN]; - - if (realpath(file, rfile) == NULL || realpath(prefix, rprefix) == NULL) - return (-1); - - return (strncmp(rfile, rprefix, strlen(rprefix)) == 0); -} - -int child_cmp(struct child *c1, struct child *c2) { if (c1->pid < c2->pid) @@ -1188,4 +1019,25 @@ imsg_dispatch(int fd, short event, void *p) imsg_event_add(iev); } +void +setup_spool(struct passwd *pw) +{ + if (mkdir(PATH_SPOOL, 0711) < 0 && errno != EEXIST) + err(1, "mkdir: %s", PATH_SPOOL); + if (chmod(PATH_SPOOL, 0711) < 0) + err(1, "chmod: %s", PATH_SPOOL); + if (chown(PATH_SPOOL, 0, 0) < 0) + err(1, "chown: %s", PATH_SPOOL); + + if (queue_be_init(PATH_SPOOL, pw->pw_uid, pw->pw_gid) < 0) + err(1, "backend init failed"); + + if (mkdir(PATH_SPOOL PATH_OFFLINE, 01777) < 0 && errno != EEXIST) + err(1, "mkdir: %s", PATH_SPOOL PATH_OFFLINE); + if (chmod(PATH_SPOOL PATH_OFFLINE, 01777) < 0) + err(1, "chmod: %s", PATH_SPOOL PATH_OFFLINE); + if (chown(PATH_SPOOL PATH_OFFLINE, 0, 0) < 0) + err(1, "chmod: %s", PATH_SPOOL PATH_OFFLINE); +} + SPLAY_GENERATE(childtree, child, entry, child_cmp); |