meillo@0: /* MasqMail meillo@0: Copyright (C) 1999-2001 Oliver Kurth meillo@0: meillo@0: This program is free software; you can redistribute it and/or modify meillo@0: it under the terms of the GNU General Public License as published by meillo@0: the Free Software Foundation; either version 2 of the License, or meillo@0: (at your option) any later version. meillo@0: meillo@0: This program is distributed in the hope that it will be useful, meillo@0: but WITHOUT ANY WARRANTY; without even the implied warranty of meillo@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the meillo@0: GNU General Public License for more details. meillo@0: meillo@0: You should have received a copy of the GNU General Public License meillo@0: along with this program; if not, write to the Free Software meillo@0: Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. meillo@0: */ meillo@0: meillo@0: #include "masqmail.h" meillo@0: #include "peopen.h" meillo@0: #include meillo@0: meillo@0: static meillo@0: void message_stream(FILE *out, message *msg, GList *hdr_list, guint flags) meillo@0: { meillo@0: time_t now = time(NULL); meillo@0: GList *node; meillo@0: meillo@0: if(flags & MSGSTR_FROMLINE){ meillo@0: fprintf(out, "From <%s@%s> %s", msg->return_path->local_part, meillo@0: msg->return_path->domain, ctime(&now)); meillo@0: } meillo@0: meillo@0: foreach(hdr_list, node){ meillo@0: header *hdr = (header *)(node->data); meillo@0: fputs(hdr->header, out); meillo@0: } meillo@0: putc('\n', out); meillo@0: foreach(msg->data_list, node){ meillo@0: /* From hack: */ meillo@0: if(flags & MSGSTR_FROMHACK){ meillo@0: if(strncmp(node->data, "From ", 5) == 0) meillo@0: putc('>', out); meillo@0: } meillo@0: fputs(node->data, out); meillo@0: } meillo@0: putc('\n', out); meillo@0: } meillo@0: meillo@0: gboolean append_file(message *msg, GList *hdr_list, gchar *user) meillo@0: { meillo@0: struct passwd *pw; meillo@0: gboolean ok = FALSE; meillo@0: meillo@0: /* headers may be special for a local delivery */ meillo@0: if(hdr_list == NULL) meillo@0: hdr_list = msg->hdr_list; meillo@0: meillo@0: if((pw = getpwnam(user))){ meillo@0: uid_t saved_uid = geteuid(); meillo@0: gid_t saved_gid = getegid(); meillo@0: gboolean uid_ok = TRUE, gid_ok = TRUE; meillo@0: meillo@0: if(!conf.run_as_user){ meillo@0: uid_ok = (seteuid(0) == 0); meillo@0: if(uid_ok){ meillo@0: gid_ok = (setegid(conf.mail_gid) == 0); meillo@0: uid_ok = (seteuid(pw->pw_uid) == 0); meillo@0: } meillo@0: } meillo@0: meillo@0: DEBUG(5) debugf("running as euid %d\n", geteuid()); meillo@0: DEBUG(5) debugf("running as egid %d\n", getegid()); meillo@0: meillo@0: if(uid_ok && gid_ok){ meillo@0: gchar *filename; meillo@0: FILE *out; meillo@0: meillo@0: filename = g_strdup_printf("%s/%s", conf.mail_dir, user); meillo@0: if((out = fopen(filename, "a"))){ meillo@0: #ifdef USE_LIBLOCKFILE meillo@0: gint err; meillo@0: /* lock file using liblockfile */ meillo@0: err = maillock(user,3); meillo@0: if(err == 0){ meillo@0: #else meillo@0: /* lock file: */ meillo@0: struct flock lock; meillo@0: lock.l_type = F_WRLCK; meillo@0: lock.l_whence = SEEK_END; meillo@0: lock.l_start = lock.l_len = 0; meillo@0: if(fcntl(fileno(out), F_SETLK, &lock) != -1){ meillo@0: #endif meillo@0: fchmod(fileno(out), 0600); meillo@0: meillo@0: message_stream(out, msg, hdr_list, MSGSTR_FROMLINE|MSGSTR_FROMHACK); meillo@0: meillo@0: ok = TRUE; meillo@0: meillo@0: /* close when still user */ meillo@0: fclose(out); meillo@0: #ifdef USE_LIBLOCKFILE meillo@0: mailunlock(); meillo@0: #endif meillo@0: }else{ meillo@0: fclose(out); meillo@0: #ifdef USE_LIBLOCKFILE meillo@0: DEBUG(3) debugf("could not lock file %s: error %d\n", meillo@0: filename, err); meillo@0: } /* XEmacs indenting convenience... */ meillo@0: #else meillo@0: DEBUG(3) debugf("could not lock file %s: %s\n", meillo@0: filename, strerror(errno)); meillo@0: } meillo@0: #endif meillo@0: }else{ meillo@0: logwrite(LOG_ALERT, "could not open file %s: %s\n", meillo@0: filename, strerror(errno)); meillo@0: } meillo@0: g_free(filename); meillo@0: meillo@0: if(!conf.run_as_user){ meillo@0: uid_ok = (seteuid(0) == 0); meillo@0: if(uid_ok){ meillo@0: gid_ok = (setegid(saved_gid) == 0); meillo@0: uid_ok = (seteuid(saved_uid) == 0); meillo@0: } meillo@0: } meillo@0: meillo@0: if(!uid_ok || !gid_ok){ meillo@0: /* FIXME: if this fails we HAVE to exit, because we shall not run meillo@0: with some users id. But we do not return, and so this message meillo@0: will not be finished, so the user will get the message again meillo@0: next time a delivery is attempted... */ meillo@0: logwrite(LOG_ALERT, meillo@0: "could not set back uid or gid after local delivery: %s\n", meillo@0: strerror(errno)); meillo@0: logwrite(LOG_ALERT, meillo@0: "uid=%d, gid=%d, euid=%d, egid=%d, want = %d, %d\n", meillo@0: getuid(), getgid(), geteuid(), getegid(), saved_uid, saved_gid); meillo@0: exit(EXIT_FAILURE); meillo@0: } meillo@0: }else{ meillo@0: logwrite(LOG_ALERT, meillo@0: "could not set uid or gid for local delivery, uid = %d: %s\n", meillo@0: pw->pw_uid, strerror(errno)); meillo@0: } meillo@0: }else{ meillo@0: logwrite(LOG_ALERT, "could not find password entry for user %s\n", user); meillo@0: errno = ENOENT; /* getpwnam does not set errno correctly */ meillo@0: } meillo@0: meillo@0: return ok; meillo@0: } meillo@0: meillo@0: #ifdef ENABLE_MAILDIR meillo@0: gboolean maildir_out(message *msg, GList *hdr_list, gchar *user, guint flags) meillo@0: { meillo@0: struct passwd *pw; meillo@0: gboolean ok = FALSE; meillo@0: meillo@0: /* headers may be special for a local delivery */ meillo@0: if(hdr_list == NULL) meillo@0: hdr_list = msg->hdr_list; meillo@0: meillo@0: if((pw = getpwnam(user))){ meillo@0: uid_t saved_uid = geteuid(); meillo@0: gid_t saved_gid = getegid(); meillo@0: gboolean uid_ok = TRUE, gid_ok = TRUE; meillo@0: meillo@0: if(!conf.run_as_user){ meillo@0: uid_ok = (seteuid(0) == 0); meillo@0: if(uid_ok){ meillo@0: gid_ok = (setegid(conf.mail_gid) == 0); meillo@0: uid_ok = (seteuid(pw->pw_uid) == 0); meillo@0: } meillo@0: } meillo@0: meillo@0: DEBUG(5) debugf("running as euid %d\n", geteuid()); meillo@0: DEBUG(5) debugf("running as egid %d\n", getegid()); meillo@0: meillo@0: if(uid_ok && gid_ok){ meillo@0: char *path = g_strdup_printf("%s/Maildir", pw->pw_dir); meillo@0: struct stat statbuf; meillo@0: int ret; meillo@0: meillo@0: DEBUG(5) debugf("path = %s\n", path); meillo@0: meillo@0: ok = TRUE; meillo@0: ret = stat(path, &statbuf); meillo@0: if(ret != 0){ meillo@0: ok = FALSE; meillo@0: if(errno == ENOENT){ meillo@0: logwrite(LOG_NOTICE, "directory %s does not exist, creating\n", path); meillo@0: if(mkdir(path, 0700) == 0) meillo@0: ok = TRUE; meillo@0: }else meillo@0: logwrite(LOG_ALERT, "stat of %s failed: %s\n", path, strerror(errno)); meillo@0: } meillo@0: if(ok){ meillo@0: ok = FALSE; meillo@0: ret = stat(path, &statbuf); meillo@0: if(S_ISDIR(statbuf.st_mode)){ meillo@0: gchar *subdirs[] = {"tmp", "new", "cur"}; meillo@0: int i; meillo@0: for(i = 0; i < 3; i++){ meillo@0: char *path1 = g_strdup_printf("%s/%s", path, subdirs[i]); meillo@0: ret = stat(path1, &statbuf); meillo@0: if(ret != 0){ meillo@0: if(errno == ENOENT){ meillo@0: logwrite(LOG_NOTICE, "directory %s does not exist, creating\n", path1); meillo@0: if(mkdir(path1, 0700) != 0) break; meillo@0: } meillo@0: } meillo@0: g_free(path1); meillo@0: } meillo@0: if(i == 3){ meillo@0: FILE *out; meillo@0: mode_t saved_mode = umask(066); meillo@0: /* the qmail style unique works only if delivering meillo@0: with different process. We do not fork for each delivery, meillo@0: so our uid is more unique. Hope it is compatible with all meillo@0: MUAs. meillo@0: */ meillo@0: gchar *filename = g_strdup_printf("%s/tmp/%s.%s", path, msg->uid, conf.host_name); meillo@0: meillo@0: DEBUG(5) debugf("filename = %s\n", filename); meillo@0: meillo@0: if((out = fopen(filename, "w"))){ meillo@0: gchar *newname = meillo@0: g_strdup_printf("%s/new/%s.%s", path, msg->uid, conf.host_name); meillo@0: message_stream(out, msg, hdr_list, flags); meillo@0: ok = TRUE; meillo@0: if(fflush(out) == EOF) ok = FALSE; meillo@0: else if(fdatasync(fileno(out)) != 0){ meillo@0: if(errno != EINVAL) /* some fs do not support this.. meillo@0: I hope this also means that it is not necessary */ meillo@0: ok = FALSE; meillo@0: } meillo@0: fclose(out); meillo@0: if(rename(filename, newname) != 0){ meillo@0: ok = FALSE; meillo@0: logwrite(LOG_ALERT, "moving %s to %s failed: %s", meillo@0: filename, newname, strerror(errno)); meillo@0: } meillo@0: g_free(newname); meillo@0: } meillo@0: umask(saved_mode); meillo@0: g_free(filename); meillo@0: } meillo@0: }else{ meillo@0: logwrite(LOG_ALERT, "%s is not a directory\n", path); meillo@0: errno = ENOTDIR; meillo@0: } meillo@0: } meillo@0: if(!conf.run_as_user){ meillo@0: uid_ok = (seteuid(0) == 0); meillo@0: if(uid_ok){ meillo@0: gid_ok = (setegid(saved_gid) == 0); meillo@0: uid_ok = (seteuid(saved_uid) == 0); meillo@0: } meillo@0: } meillo@0: if(!uid_ok || !gid_ok){ meillo@0: /* FIXME: if this fails we HAVE to exit, because we shall not run meillo@0: with some users id. But we do not return, and so this message meillo@0: will not be finished, so the user will get the message again meillo@0: next time a delivery is attempted... */ meillo@0: logwrite(LOG_ALERT, meillo@0: "could not set back uid or gid after local delivery: %s\n", meillo@0: strerror(errno)); meillo@0: exit(EXIT_FAILURE); meillo@0: } meillo@0: g_free(path); meillo@0: }else{ meillo@0: logwrite(LOG_ALERT, meillo@0: "could not set uid or gid for local delivery, uid = %d: %s\n", meillo@0: pw->pw_uid, strerror(errno)); meillo@0: } meillo@0: }else{ meillo@0: logwrite(LOG_ALERT, "could not find password entry for user %s\n", user); meillo@0: errno = ENOENT; /* getpwnam does not set errno correctly */ meillo@0: } meillo@0: return ok; meillo@0: } meillo@0: #endif meillo@0: meillo@0: gboolean meillo@0: pipe_out(message *msg, GList *hdr_list, address *rcpt, gchar *cmd, guint flags) meillo@0: { meillo@0: gchar *envp[40]; meillo@0: FILE *out; meillo@0: uid_t saved_uid = geteuid(); meillo@0: gid_t saved_gid = getegid(); meillo@0: gboolean ok = FALSE; meillo@0: gint i, n; meillo@0: pid_t pid; meillo@0: void (*old_signal)(int); meillo@0: int status; meillo@0: meillo@0: /* set uid and gid to the mail ids */ meillo@0: if(!conf.run_as_user){ meillo@0: set_euidgid(conf.mail_uid, conf.mail_gid, &saved_uid, &saved_gid); meillo@0: } meillo@0: meillo@0: /* set environment */ meillo@0: { meillo@0: gint i = 0; meillo@0: address *ancestor = addr_find_ancestor(rcpt); meillo@0: meillo@0: envp[i++] = g_strdup_printf("SENDER=%s@%s", msg->return_path->local_part, msg->return_path->domain); meillo@0: envp[i++] = g_strdup_printf("SENDER_DOMAIN=%s", msg->return_path->domain); meillo@0: envp[i++] = g_strdup_printf("SENDER_LOCAL=%s", msg->return_path->local_part); meillo@0: envp[i++] = g_strdup_printf("RECEIVED_HOST=%s", msg->received_host ? msg->received_host : ""); meillo@0: meillo@0: envp[i++] = g_strdup_printf("RETURN_PATH=%s@%s", meillo@0: msg->return_path->local_part, msg->return_path->domain); meillo@0: envp[i++] = g_strdup_printf("DOMAIN=%s", ancestor->domain); meillo@0: meillo@0: envp[i++] = g_strdup_printf("LOCAL_PART=%s", ancestor->local_part); meillo@0: envp[i++] = g_strdup_printf("USER=%s", ancestor->local_part); meillo@0: envp[i++] = g_strdup_printf("LOGNAME=%s", ancestor->local_part); meillo@0: meillo@0: envp[i++] = g_strdup_printf("MESSAGE_ID=%s", msg->uid); meillo@0: envp[i++] = g_strdup_printf("QUALIFY_DOMAIN=%s", conf.host_name); meillo@0: meillo@0: envp[i] = NULL; meillo@0: n = i; meillo@0: } meillo@0: meillo@0: old_signal = signal(SIGCHLD, SIG_DFL); meillo@0: meillo@0: out = peidopen(cmd, "w", envp, &pid, conf.mail_uid, conf.mail_gid); meillo@0: if(out != NULL){ meillo@0: message_stream(out, msg, hdr_list, flags); meillo@0: meillo@0: fclose(out); meillo@0: meillo@0: waitpid(pid, &status, 0); meillo@0: meillo@0: if(WEXITSTATUS(status) != 0){ meillo@0: int exstat = WEXITSTATUS(status); meillo@0: logwrite(LOG_ALERT, "process returned %d (%s)\n", exstat, ext_strerror(1024 + exstat)); meillo@0: errno = 1024 + exstat; meillo@0: }else if(WIFSIGNALED(status)){ meillo@0: logwrite(LOG_ALERT, "process got signal %d\n", WTERMSIG(status)); meillo@0: }else meillo@0: ok = TRUE; meillo@0: meillo@0: }else meillo@0: logwrite(LOG_ALERT, "could not open pipe '%s': %s\n", cmd, strerror(errno)); meillo@0: meillo@0: signal(SIGCHLD, old_signal); meillo@0: meillo@0: /* free environment */ meillo@0: for(i = 0; i < n; i++){ meillo@0: g_free(envp[i]); meillo@0: } meillo@0: meillo@0: /* set uid and gid back */ meillo@0: if(!conf.run_as_user){ meillo@0: set_euidgid(saved_uid, saved_gid, NULL, NULL); meillo@0: } meillo@0: meillo@0: return ok; meillo@0: } meillo@0: