masqmail

annotate src/local.c @ 156:ee2afbf92428

require host_name to be set in config file exit otherwise there is no portable way to determine the hostname (actually the hostname that masqmail should use) thus it must be set by the administrator
author meillo@marmaro.de
date Thu, 08 Jul 2010 09:49:05 +0200
parents f671821d8222
children d1c53e76096f
rev   line source
meillo@0 1 /* MasqMail
meillo@0 2 Copyright (C) 1999-2001 Oliver Kurth
meillo@0 3
meillo@0 4 This program is free software; you can redistribute it and/or modify
meillo@0 5 it under the terms of the GNU General Public License as published by
meillo@0 6 the Free Software Foundation; either version 2 of the License, or
meillo@0 7 (at your option) any later version.
meillo@0 8
meillo@0 9 This program is distributed in the hope that it will be useful,
meillo@0 10 but WITHOUT ANY WARRANTY; without even the implied warranty of
meillo@0 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
meillo@0 12 GNU General Public License for more details.
meillo@0 13
meillo@0 14 You should have received a copy of the GNU General Public License
meillo@0 15 along with this program; if not, write to the Free Software
meillo@0 16 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
meillo@0 17 */
meillo@0 18
meillo@15 19 #include <sys/wait.h>
meillo@15 20
meillo@0 21 #include "masqmail.h"
meillo@0 22 #include "peopen.h"
meillo@0 23
meillo@10 24 static void
meillo@10 25 message_stream(FILE * out, message * msg, GList * hdr_list, guint flags)
meillo@0 26 {
meillo@10 27 time_t now = time(NULL);
meillo@10 28 GList *node;
meillo@10 29
meillo@10 30 if (flags & MSGSTR_FROMLINE) {
meillo@10 31 fprintf(out, "From <%s@%s> %s", msg->return_path->local_part, msg->return_path->domain, ctime(&now));
meillo@10 32 }
meillo@10 33
meillo@10 34 foreach(hdr_list, node) {
meillo@10 35 header *hdr = (header *) (node->data);
meillo@10 36 fputs(hdr->header, out);
meillo@10 37 }
meillo@10 38 putc('\n', out);
meillo@10 39 foreach(msg->data_list, node) {
meillo@10 40 /* From hack: */
meillo@10 41 if (flags & MSGSTR_FROMHACK) {
meillo@10 42 if (strncmp(node->data, "From ", 5) == 0)
meillo@10 43 putc('>', out);
meillo@10 44 }
meillo@10 45 fputs(node->data, out);
meillo@10 46 }
meillo@10 47 putc('\n', out);
meillo@0 48 }
meillo@0 49
meillo@10 50 gboolean
meillo@10 51 append_file(message * msg, GList * hdr_list, gchar * user)
meillo@0 52 {
meillo@10 53 struct passwd *pw;
meillo@10 54 gboolean ok = FALSE;
meillo@0 55
meillo@10 56 /* headers may be special for a local delivery */
meillo@10 57 if (hdr_list == NULL)
meillo@10 58 hdr_list = msg->hdr_list;
meillo@0 59
meillo@10 60 if ((pw = getpwnam(user))) {
meillo@10 61 uid_t saved_uid = geteuid();
meillo@10 62 gid_t saved_gid = getegid();
meillo@10 63 gboolean uid_ok = TRUE, gid_ok = TRUE;
meillo@0 64
meillo@10 65 if (!conf.run_as_user) {
meillo@10 66 uid_ok = (seteuid(0) == 0);
meillo@10 67 if (uid_ok) {
meillo@10 68 gid_ok = (setegid(conf.mail_gid) == 0);
meillo@10 69 uid_ok = (seteuid(pw->pw_uid) == 0);
meillo@10 70 }
meillo@10 71 }
meillo@0 72
meillo@114 73 DEBUG(5) debugf("running as euid %d, egid %d\n", geteuid(), getegid());
meillo@0 74
meillo@10 75 if (uid_ok && gid_ok) {
meillo@10 76 gchar *filename;
meillo@10 77 FILE *out;
meillo@10 78
meillo@10 79 filename = g_strdup_printf("%s/%s", conf.mail_dir, user);
meillo@10 80 if ((out = fopen(filename, "a"))) {
meillo@0 81 #ifdef USE_LIBLOCKFILE
meillo@10 82 gint err;
meillo@10 83 /* lock file using liblockfile */
meillo@10 84 err = maillock(user, 3);
meillo@10 85 if (err == 0) {
meillo@0 86 #else
meillo@10 87 /* lock file: */
meillo@10 88 struct flock lock;
meillo@10 89 lock.l_type = F_WRLCK;
meillo@10 90 lock.l_whence = SEEK_END;
meillo@10 91 lock.l_start = lock.l_len = 0;
meillo@10 92 if (fcntl(fileno(out), F_SETLK, &lock) != -1) {
meillo@0 93 #endif
meillo@10 94 fchmod(fileno(out), 0600);
meillo@10 95 message_stream(out, msg, hdr_list, MSGSTR_FROMLINE | MSGSTR_FROMHACK);
meillo@10 96 ok = TRUE;
meillo@10 97
meillo@10 98 /* close when still user */
meillo@10 99 fclose(out);
meillo@0 100 #ifdef USE_LIBLOCKFILE
meillo@10 101 mailunlock();
meillo@0 102 #endif
meillo@10 103 } else {
meillo@10 104 fclose(out);
meillo@0 105 #ifdef USE_LIBLOCKFILE
meillo@10 106 DEBUG(3) debugf("could not lock file %s: error %d\n", filename, err);
meillo@10 107 } /* XEmacs indenting convenience... */
meillo@0 108 #else
meillo@10 109 DEBUG(3) debugf("could not lock file %s: %s\n", filename, strerror(errno));
meillo@10 110 }
meillo@10 111 #endif
meillo@10 112 } else {
meillo@10 113 logwrite(LOG_ALERT, "could not open file %s: %s\n", filename, strerror(errno));
meillo@10 114 }
meillo@10 115 g_free(filename);
meillo@10 116
meillo@10 117 if (!conf.run_as_user) {
meillo@10 118 uid_ok = (seteuid(0) == 0);
meillo@10 119 if (uid_ok) {
meillo@10 120 gid_ok = (setegid(saved_gid) == 0);
meillo@10 121 uid_ok = (seteuid(saved_uid) == 0);
meillo@10 122 }
meillo@10 123 }
meillo@10 124
meillo@10 125 if (!uid_ok || !gid_ok) {
meillo@10 126 /* FIXME: if this fails we HAVE to exit, because we shall not run
meillo@10 127 with some users id. But we do not return, and so this message
meillo@10 128 will not be finished, so the user will get the message again
meillo@10 129 next time a delivery is attempted... */
meillo@10 130 logwrite(LOG_ALERT, "could not set back uid or gid after local delivery: %s\n", strerror(errno));
meillo@10 131 logwrite(LOG_ALERT, "uid=%d, gid=%d, euid=%d, egid=%d, want = %d, %d\n",
meillo@10 132 getuid(), getgid(), geteuid(), getegid(), saved_uid, saved_gid);
meillo@10 133 exit(EXIT_FAILURE);
meillo@10 134 }
meillo@10 135 } else {
meillo@10 136 logwrite(LOG_ALERT, "could not set uid or gid for local delivery, uid = %d: %s\n", pw->pw_uid, strerror(errno));
meillo@10 137 }
meillo@10 138 } else {
meillo@10 139 logwrite(LOG_ALERT, "could not find password entry for user %s\n", user);
meillo@10 140 errno = ENOENT; /* getpwnam does not set errno correctly */
meillo@0 141 }
meillo@0 142
meillo@10 143 return ok;
meillo@0 144 }
meillo@0 145
meillo@0 146 #ifdef ENABLE_MAILDIR
meillo@10 147 gboolean
meillo@10 148 maildir_out(message * msg, GList * hdr_list, gchar * user, guint flags)
meillo@0 149 {
meillo@10 150 struct passwd *pw;
meillo@10 151 gboolean ok = FALSE;
meillo@0 152
meillo@10 153 /* headers may be special for a local delivery */
meillo@10 154 if (hdr_list == NULL)
meillo@10 155 hdr_list = msg->hdr_list;
meillo@0 156
meillo@10 157 if ((pw = getpwnam(user))) {
meillo@10 158 uid_t saved_uid = geteuid();
meillo@10 159 gid_t saved_gid = getegid();
meillo@10 160 gboolean uid_ok = TRUE, gid_ok = TRUE;
meillo@0 161
meillo@10 162 if (!conf.run_as_user) {
meillo@10 163 uid_ok = (seteuid(0) == 0);
meillo@10 164 if (uid_ok) {
meillo@10 165 gid_ok = (setegid(conf.mail_gid) == 0);
meillo@10 166 uid_ok = (seteuid(pw->pw_uid) == 0);
meillo@10 167 }
meillo@10 168 }
meillo@0 169
meillo@114 170 DEBUG(5) debugf("running as euid %d, egid %d\n", geteuid(), getegid());
meillo@0 171
meillo@10 172 if (uid_ok && gid_ok) {
meillo@10 173 char *path = g_strdup_printf("%s/Maildir", pw->pw_dir);
meillo@10 174 struct stat statbuf;
meillo@10 175 int ret;
meillo@0 176
meillo@114 177 DEBUG(5) debugf(" path = %s\n", path);
meillo@10 178
meillo@10 179 ok = TRUE;
meillo@10 180 ret = stat(path, &statbuf);
meillo@10 181 if (ret != 0) {
meillo@10 182 ok = FALSE;
meillo@10 183 if (errno == ENOENT) {
meillo@10 184 logwrite(LOG_NOTICE, "directory %s does not exist, creating\n", path);
meillo@10 185 if (mkdir(path, 0700) == 0)
meillo@10 186 ok = TRUE;
meillo@10 187 } else
meillo@10 188 logwrite(LOG_ALERT, "stat of %s failed: %s\n", path, strerror(errno));
meillo@10 189 }
meillo@10 190 if (ok) {
meillo@10 191 ok = FALSE;
meillo@10 192 ret = stat(path, &statbuf);
meillo@10 193 if (S_ISDIR(statbuf.st_mode)) {
meillo@10 194 gchar *subdirs[] = { "tmp", "new", "cur" };
meillo@10 195 int i;
meillo@10 196 for (i = 0; i < 3; i++) {
meillo@10 197 char *path1 = g_strdup_printf("%s/%s", path, subdirs[i]);
meillo@10 198 ret = stat(path1, &statbuf);
meillo@10 199 if (ret != 0) {
meillo@10 200 if (errno == ENOENT) {
meillo@10 201 logwrite(LOG_NOTICE, "directory %s does not exist, creating\n", path1);
meillo@10 202 if (mkdir(path1, 0700) != 0)
meillo@10 203 break;
meillo@10 204 }
meillo@10 205 }
meillo@10 206 g_free(path1);
meillo@10 207 }
meillo@10 208 if (i == 3) {
meillo@10 209 FILE *out;
meillo@10 210 mode_t saved_mode = umask(066);
meillo@15 211 /* the qmail style unique works only if delivering with different process.
meillo@15 212 We do not fork for each delivery, so our uid is more unique.
meillo@15 213 Hope it is compatible with all MUAs.
meillo@10 214 */
meillo@10 215 gchar *filename = g_strdup_printf("%s/tmp/%s.%s", path, msg->uid, conf.host_name);
meillo@10 216
meillo@10 217 DEBUG(5) debugf("filename = %s\n", filename);
meillo@10 218
meillo@10 219 if ((out = fopen(filename, "w"))) {
meillo@10 220 gchar *newname = g_strdup_printf("%s/new/%s.%s", path, msg->uid, conf.host_name);
meillo@10 221 message_stream(out, msg, hdr_list, flags);
meillo@10 222 ok = TRUE;
meillo@10 223 if (fflush(out) == EOF)
meillo@10 224 ok = FALSE;
meillo@10 225 else if (fdatasync(fileno(out)) != 0) {
meillo@15 226 if (errno != EINVAL)
meillo@15 227 /* some fs do not support this.. I hope this also means that it is not necessary */
meillo@10 228 ok = FALSE;
meillo@10 229 }
meillo@10 230 fclose(out);
meillo@10 231 if (rename(filename, newname) != 0) {
meillo@10 232 ok = FALSE;
meillo@10 233 logwrite(LOG_ALERT, "moving %s to %s failed: %s", filename, newname, strerror(errno));
meillo@10 234 }
meillo@10 235 g_free(newname);
meillo@10 236 }
meillo@10 237 umask(saved_mode);
meillo@10 238 g_free(filename);
meillo@10 239 }
meillo@10 240 } else {
meillo@10 241 logwrite(LOG_ALERT, "%s is not a directory\n", path);
meillo@10 242 errno = ENOTDIR;
meillo@10 243 }
meillo@10 244 }
meillo@10 245 if (!conf.run_as_user) {
meillo@10 246 uid_ok = (seteuid(0) == 0);
meillo@10 247 if (uid_ok) {
meillo@10 248 gid_ok = (setegid(saved_gid) == 0);
meillo@10 249 uid_ok = (seteuid(saved_uid) == 0);
meillo@10 250 }
meillo@10 251 }
meillo@10 252 if (!uid_ok || !gid_ok) {
meillo@10 253 /* FIXME: if this fails we HAVE to exit, because we shall not run
meillo@10 254 with some users id. But we do not return, and so this message
meillo@10 255 will not be finished, so the user will get the message again
meillo@10 256 next time a delivery is attempted... */
meillo@10 257 logwrite(LOG_ALERT, "could not set back uid or gid after local delivery: %s\n", strerror(errno));
meillo@10 258 exit(EXIT_FAILURE);
meillo@10 259 }
meillo@10 260 g_free(path);
meillo@10 261 } else {
meillo@10 262 logwrite(LOG_ALERT, "could not set uid or gid for local delivery, uid = %d: %s\n", pw->pw_uid, strerror(errno));
meillo@10 263 }
meillo@10 264 } else {
meillo@10 265 logwrite(LOG_ALERT, "could not find password entry for user %s\n", user);
meillo@10 266 errno = ENOENT; /* getpwnam does not set errno correctly */
meillo@0 267 }
meillo@10 268 return ok;
meillo@0 269 }
meillo@0 270 #endif
meillo@0 271
meillo@0 272 gboolean
meillo@10 273 pipe_out(message * msg, GList * hdr_list, address * rcpt, gchar * cmd, guint flags)
meillo@0 274 {
meillo@10 275 gchar *envp[40];
meillo@10 276 FILE *out;
meillo@10 277 uid_t saved_uid = geteuid();
meillo@10 278 gid_t saved_gid = getegid();
meillo@10 279 gboolean ok = FALSE;
meillo@10 280 gint i, n;
meillo@10 281 pid_t pid;
meillo@10 282 void (*old_signal) (int);
meillo@10 283 int status;
meillo@0 284
meillo@10 285 /* set uid and gid to the mail ids */
meillo@10 286 if (!conf.run_as_user) {
meillo@10 287 set_euidgid(conf.mail_uid, conf.mail_gid, &saved_uid, &saved_gid);
meillo@10 288 }
meillo@0 289
meillo@10 290 /* set environment */
meillo@10 291 {
meillo@10 292 gint i = 0;
meillo@10 293 address *ancestor = addr_find_ancestor(rcpt);
meillo@0 294
meillo@10 295 envp[i++] = g_strdup_printf("SENDER=%s@%s", msg->return_path->local_part, msg->return_path->domain);
meillo@10 296 envp[i++] = g_strdup_printf("SENDER_DOMAIN=%s", msg->return_path->domain);
meillo@10 297 envp[i++] = g_strdup_printf("SENDER_LOCAL=%s", msg->return_path->local_part);
meillo@10 298 envp[i++] = g_strdup_printf("RECEIVED_HOST=%s", msg->received_host ? msg->received_host : "");
meillo@0 299
meillo@10 300 envp[i++] = g_strdup_printf("RETURN_PATH=%s@%s", msg->return_path->local_part, msg->return_path->domain);
meillo@10 301 envp[i++] = g_strdup_printf("DOMAIN=%s", ancestor->domain);
meillo@0 302
meillo@10 303 envp[i++] = g_strdup_printf("LOCAL_PART=%s", ancestor->local_part);
meillo@10 304 envp[i++] = g_strdup_printf("USER=%s", ancestor->local_part);
meillo@10 305 envp[i++] = g_strdup_printf("LOGNAME=%s", ancestor->local_part);
meillo@0 306
meillo@10 307 envp[i++] = g_strdup_printf("MESSAGE_ID=%s", msg->uid);
meillo@10 308 envp[i++] = g_strdup_printf("QUALIFY_DOMAIN=%s", conf.host_name);
meillo@0 309
meillo@10 310 envp[i] = NULL;
meillo@10 311 n = i;
meillo@10 312 }
meillo@0 313
meillo@10 314 old_signal = signal(SIGCHLD, SIG_DFL);
meillo@0 315
meillo@10 316 out = peidopen(cmd, "w", envp, &pid, conf.mail_uid, conf.mail_gid);
meillo@10 317 if (out != NULL) {
meillo@10 318 message_stream(out, msg, hdr_list, flags);
meillo@0 319
meillo@10 320 fclose(out);
meillo@0 321
meillo@10 322 waitpid(pid, &status, 0);
meillo@0 323
meillo@10 324 if (WEXITSTATUS(status) != 0) {
meillo@10 325 int exstat = WEXITSTATUS(status);
meillo@10 326 logwrite(LOG_ALERT, "process returned %d (%s)\n", exstat, ext_strerror(1024 + exstat));
meillo@10 327 errno = 1024 + exstat;
meillo@10 328 } else if (WIFSIGNALED(status)) {
meillo@10 329 logwrite(LOG_ALERT, "process got signal %d\n", WTERMSIG(status));
meillo@10 330 } else
meillo@10 331 ok = TRUE;
meillo@0 332
meillo@10 333 } else
meillo@10 334 logwrite(LOG_ALERT, "could not open pipe '%s': %s\n", cmd, strerror(errno));
meillo@0 335
meillo@10 336 signal(SIGCHLD, old_signal);
meillo@0 337
meillo@10 338 /* free environment */
meillo@10 339 for (i = 0; i < n; i++) {
meillo@10 340 g_free(envp[i]);
meillo@10 341 }
meillo@0 342
meillo@10 343 /* set uid and gid back */
meillo@10 344 if (!conf.run_as_user) {
meillo@10 345 set_euidgid(saved_uid, saved_gid, NULL, NULL);
meillo@10 346 }
meillo@0 347
meillo@10 348 return ok;
meillo@0 349 }