masqmail-0.2

annotate src/local.c @ 115:315773f814f7

allow `:' unquoted too; updated masqmail.conf.5 (Thanks to Paolo) rvalues with colons were probably already read correctly although unquoted
author meillo@marmaro.de
date Wed, 30 Jun 2010 15:31:42 +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 }