masqmail

annotate src/local.c @ 91:3e7136221104

correct masqmail path in rmail script; remove docs on uninstall on install the correct path to the masqmail executable gets inserted into the rmail script now. now documentation, examples, and the templates are removed on uninstall. Empty directories are the only thing that may remain if one installs masqmail into an unusual path.
author meillo@marmaro.de
date Mon, 21 Jun 2010 09:40:16 +0200
parents 26e34ae9a3e3
children a80ebfa16cd5
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@10 73 DEBUG(5) debugf("running as euid %d\n", geteuid());
meillo@10 74 DEBUG(5) debugf("running as egid %d\n", getegid());
meillo@0 75
meillo@10 76 if (uid_ok && gid_ok) {
meillo@10 77 gchar *filename;
meillo@10 78 FILE *out;
meillo@10 79
meillo@10 80 filename = g_strdup_printf("%s/%s", conf.mail_dir, user);
meillo@10 81 if ((out = fopen(filename, "a"))) {
meillo@0 82 #ifdef USE_LIBLOCKFILE
meillo@10 83 gint err;
meillo@10 84 /* lock file using liblockfile */
meillo@10 85 err = maillock(user, 3);
meillo@10 86 if (err == 0) {
meillo@0 87 #else
meillo@10 88 /* lock file: */
meillo@10 89 struct flock lock;
meillo@10 90 lock.l_type = F_WRLCK;
meillo@10 91 lock.l_whence = SEEK_END;
meillo@10 92 lock.l_start = lock.l_len = 0;
meillo@10 93 if (fcntl(fileno(out), F_SETLK, &lock) != -1) {
meillo@0 94 #endif
meillo@10 95 fchmod(fileno(out), 0600);
meillo@10 96 message_stream(out, msg, hdr_list, MSGSTR_FROMLINE | MSGSTR_FROMHACK);
meillo@10 97 ok = TRUE;
meillo@10 98
meillo@10 99 /* close when still user */
meillo@10 100 fclose(out);
meillo@0 101 #ifdef USE_LIBLOCKFILE
meillo@10 102 mailunlock();
meillo@0 103 #endif
meillo@10 104 } else {
meillo@10 105 fclose(out);
meillo@0 106 #ifdef USE_LIBLOCKFILE
meillo@10 107 DEBUG(3) debugf("could not lock file %s: error %d\n", filename, err);
meillo@10 108 } /* XEmacs indenting convenience... */
meillo@0 109 #else
meillo@10 110 DEBUG(3) debugf("could not lock file %s: %s\n", filename, strerror(errno));
meillo@10 111 }
meillo@10 112 #endif
meillo@10 113 } else {
meillo@10 114 logwrite(LOG_ALERT, "could not open file %s: %s\n", filename, strerror(errno));
meillo@10 115 }
meillo@10 116 g_free(filename);
meillo@10 117
meillo@10 118 if (!conf.run_as_user) {
meillo@10 119 uid_ok = (seteuid(0) == 0);
meillo@10 120 if (uid_ok) {
meillo@10 121 gid_ok = (setegid(saved_gid) == 0);
meillo@10 122 uid_ok = (seteuid(saved_uid) == 0);
meillo@10 123 }
meillo@10 124 }
meillo@10 125
meillo@10 126 if (!uid_ok || !gid_ok) {
meillo@10 127 /* FIXME: if this fails we HAVE to exit, because we shall not run
meillo@10 128 with some users id. But we do not return, and so this message
meillo@10 129 will not be finished, so the user will get the message again
meillo@10 130 next time a delivery is attempted... */
meillo@10 131 logwrite(LOG_ALERT, "could not set back uid or gid after local delivery: %s\n", strerror(errno));
meillo@10 132 logwrite(LOG_ALERT, "uid=%d, gid=%d, euid=%d, egid=%d, want = %d, %d\n",
meillo@10 133 getuid(), getgid(), geteuid(), getegid(), saved_uid, saved_gid);
meillo@10 134 exit(EXIT_FAILURE);
meillo@10 135 }
meillo@10 136 } else {
meillo@10 137 logwrite(LOG_ALERT, "could not set uid or gid for local delivery, uid = %d: %s\n", pw->pw_uid, strerror(errno));
meillo@10 138 }
meillo@10 139 } else {
meillo@10 140 logwrite(LOG_ALERT, "could not find password entry for user %s\n", user);
meillo@10 141 errno = ENOENT; /* getpwnam does not set errno correctly */
meillo@0 142 }
meillo@0 143
meillo@10 144 return ok;
meillo@0 145 }
meillo@0 146
meillo@0 147 #ifdef ENABLE_MAILDIR
meillo@10 148 gboolean
meillo@10 149 maildir_out(message * msg, GList * hdr_list, gchar * user, guint flags)
meillo@0 150 {
meillo@10 151 struct passwd *pw;
meillo@10 152 gboolean ok = FALSE;
meillo@0 153
meillo@10 154 /* headers may be special for a local delivery */
meillo@10 155 if (hdr_list == NULL)
meillo@10 156 hdr_list = msg->hdr_list;
meillo@0 157
meillo@10 158 if ((pw = getpwnam(user))) {
meillo@10 159 uid_t saved_uid = geteuid();
meillo@10 160 gid_t saved_gid = getegid();
meillo@10 161 gboolean uid_ok = TRUE, gid_ok = TRUE;
meillo@0 162
meillo@10 163 if (!conf.run_as_user) {
meillo@10 164 uid_ok = (seteuid(0) == 0);
meillo@10 165 if (uid_ok) {
meillo@10 166 gid_ok = (setegid(conf.mail_gid) == 0);
meillo@10 167 uid_ok = (seteuid(pw->pw_uid) == 0);
meillo@10 168 }
meillo@10 169 }
meillo@0 170
meillo@10 171 DEBUG(5) debugf("running as euid %d\n", geteuid());
meillo@10 172 DEBUG(5) debugf("running as egid %d\n", getegid());
meillo@0 173
meillo@10 174 if (uid_ok && gid_ok) {
meillo@10 175 char *path = g_strdup_printf("%s/Maildir", pw->pw_dir);
meillo@10 176 struct stat statbuf;
meillo@10 177 int ret;
meillo@0 178
meillo@10 179 DEBUG(5) debugf("path = %s\n", path);
meillo@10 180
meillo@10 181 ok = TRUE;
meillo@10 182 ret = stat(path, &statbuf);
meillo@10 183 if (ret != 0) {
meillo@10 184 ok = FALSE;
meillo@10 185 if (errno == ENOENT) {
meillo@10 186 logwrite(LOG_NOTICE, "directory %s does not exist, creating\n", path);
meillo@10 187 if (mkdir(path, 0700) == 0)
meillo@10 188 ok = TRUE;
meillo@10 189 } else
meillo@10 190 logwrite(LOG_ALERT, "stat of %s failed: %s\n", path, strerror(errno));
meillo@10 191 }
meillo@10 192 if (ok) {
meillo@10 193 ok = FALSE;
meillo@10 194 ret = stat(path, &statbuf);
meillo@10 195 if (S_ISDIR(statbuf.st_mode)) {
meillo@10 196 gchar *subdirs[] = { "tmp", "new", "cur" };
meillo@10 197 int i;
meillo@10 198 for (i = 0; i < 3; i++) {
meillo@10 199 char *path1 = g_strdup_printf("%s/%s", path, subdirs[i]);
meillo@10 200 ret = stat(path1, &statbuf);
meillo@10 201 if (ret != 0) {
meillo@10 202 if (errno == ENOENT) {
meillo@10 203 logwrite(LOG_NOTICE, "directory %s does not exist, creating\n", path1);
meillo@10 204 if (mkdir(path1, 0700) != 0)
meillo@10 205 break;
meillo@10 206 }
meillo@10 207 }
meillo@10 208 g_free(path1);
meillo@10 209 }
meillo@10 210 if (i == 3) {
meillo@10 211 FILE *out;
meillo@10 212 mode_t saved_mode = umask(066);
meillo@15 213 /* the qmail style unique works only if delivering with different process.
meillo@15 214 We do not fork for each delivery, so our uid is more unique.
meillo@15 215 Hope it is compatible with all MUAs.
meillo@10 216 */
meillo@10 217 gchar *filename = g_strdup_printf("%s/tmp/%s.%s", path, msg->uid, conf.host_name);
meillo@10 218
meillo@10 219 DEBUG(5) debugf("filename = %s\n", filename);
meillo@10 220
meillo@10 221 if ((out = fopen(filename, "w"))) {
meillo@10 222 gchar *newname = g_strdup_printf("%s/new/%s.%s", path, msg->uid, conf.host_name);
meillo@10 223 message_stream(out, msg, hdr_list, flags);
meillo@10 224 ok = TRUE;
meillo@10 225 if (fflush(out) == EOF)
meillo@10 226 ok = FALSE;
meillo@10 227 else if (fdatasync(fileno(out)) != 0) {
meillo@15 228 if (errno != EINVAL)
meillo@15 229 /* some fs do not support this.. I hope this also means that it is not necessary */
meillo@10 230 ok = FALSE;
meillo@10 231 }
meillo@10 232 fclose(out);
meillo@10 233 if (rename(filename, newname) != 0) {
meillo@10 234 ok = FALSE;
meillo@10 235 logwrite(LOG_ALERT, "moving %s to %s failed: %s", filename, newname, strerror(errno));
meillo@10 236 }
meillo@10 237 g_free(newname);
meillo@10 238 }
meillo@10 239 umask(saved_mode);
meillo@10 240 g_free(filename);
meillo@10 241 }
meillo@10 242 } else {
meillo@10 243 logwrite(LOG_ALERT, "%s is not a directory\n", path);
meillo@10 244 errno = ENOTDIR;
meillo@10 245 }
meillo@10 246 }
meillo@10 247 if (!conf.run_as_user) {
meillo@10 248 uid_ok = (seteuid(0) == 0);
meillo@10 249 if (uid_ok) {
meillo@10 250 gid_ok = (setegid(saved_gid) == 0);
meillo@10 251 uid_ok = (seteuid(saved_uid) == 0);
meillo@10 252 }
meillo@10 253 }
meillo@10 254 if (!uid_ok || !gid_ok) {
meillo@10 255 /* FIXME: if this fails we HAVE to exit, because we shall not run
meillo@10 256 with some users id. But we do not return, and so this message
meillo@10 257 will not be finished, so the user will get the message again
meillo@10 258 next time a delivery is attempted... */
meillo@10 259 logwrite(LOG_ALERT, "could not set back uid or gid after local delivery: %s\n", strerror(errno));
meillo@10 260 exit(EXIT_FAILURE);
meillo@10 261 }
meillo@10 262 g_free(path);
meillo@10 263 } else {
meillo@10 264 logwrite(LOG_ALERT, "could not set uid or gid for local delivery, uid = %d: %s\n", pw->pw_uid, strerror(errno));
meillo@10 265 }
meillo@10 266 } else {
meillo@10 267 logwrite(LOG_ALERT, "could not find password entry for user %s\n", user);
meillo@10 268 errno = ENOENT; /* getpwnam does not set errno correctly */
meillo@0 269 }
meillo@10 270 return ok;
meillo@0 271 }
meillo@0 272 #endif
meillo@0 273
meillo@0 274 gboolean
meillo@10 275 pipe_out(message * msg, GList * hdr_list, address * rcpt, gchar * cmd, guint flags)
meillo@0 276 {
meillo@10 277 gchar *envp[40];
meillo@10 278 FILE *out;
meillo@10 279 uid_t saved_uid = geteuid();
meillo@10 280 gid_t saved_gid = getegid();
meillo@10 281 gboolean ok = FALSE;
meillo@10 282 gint i, n;
meillo@10 283 pid_t pid;
meillo@10 284 void (*old_signal) (int);
meillo@10 285 int status;
meillo@0 286
meillo@10 287 /* set uid and gid to the mail ids */
meillo@10 288 if (!conf.run_as_user) {
meillo@10 289 set_euidgid(conf.mail_uid, conf.mail_gid, &saved_uid, &saved_gid);
meillo@10 290 }
meillo@0 291
meillo@10 292 /* set environment */
meillo@10 293 {
meillo@10 294 gint i = 0;
meillo@10 295 address *ancestor = addr_find_ancestor(rcpt);
meillo@0 296
meillo@10 297 envp[i++] = g_strdup_printf("SENDER=%s@%s", msg->return_path->local_part, msg->return_path->domain);
meillo@10 298 envp[i++] = g_strdup_printf("SENDER_DOMAIN=%s", msg->return_path->domain);
meillo@10 299 envp[i++] = g_strdup_printf("SENDER_LOCAL=%s", msg->return_path->local_part);
meillo@10 300 envp[i++] = g_strdup_printf("RECEIVED_HOST=%s", msg->received_host ? msg->received_host : "");
meillo@0 301
meillo@10 302 envp[i++] = g_strdup_printf("RETURN_PATH=%s@%s", msg->return_path->local_part, msg->return_path->domain);
meillo@10 303 envp[i++] = g_strdup_printf("DOMAIN=%s", ancestor->domain);
meillo@0 304
meillo@10 305 envp[i++] = g_strdup_printf("LOCAL_PART=%s", ancestor->local_part);
meillo@10 306 envp[i++] = g_strdup_printf("USER=%s", ancestor->local_part);
meillo@10 307 envp[i++] = g_strdup_printf("LOGNAME=%s", ancestor->local_part);
meillo@0 308
meillo@10 309 envp[i++] = g_strdup_printf("MESSAGE_ID=%s", msg->uid);
meillo@10 310 envp[i++] = g_strdup_printf("QUALIFY_DOMAIN=%s", conf.host_name);
meillo@0 311
meillo@10 312 envp[i] = NULL;
meillo@10 313 n = i;
meillo@10 314 }
meillo@0 315
meillo@10 316 old_signal = signal(SIGCHLD, SIG_DFL);
meillo@0 317
meillo@10 318 out = peidopen(cmd, "w", envp, &pid, conf.mail_uid, conf.mail_gid);
meillo@10 319 if (out != NULL) {
meillo@10 320 message_stream(out, msg, hdr_list, flags);
meillo@0 321
meillo@10 322 fclose(out);
meillo@0 323
meillo@10 324 waitpid(pid, &status, 0);
meillo@0 325
meillo@10 326 if (WEXITSTATUS(status) != 0) {
meillo@10 327 int exstat = WEXITSTATUS(status);
meillo@10 328 logwrite(LOG_ALERT, "process returned %d (%s)\n", exstat, ext_strerror(1024 + exstat));
meillo@10 329 errno = 1024 + exstat;
meillo@10 330 } else if (WIFSIGNALED(status)) {
meillo@10 331 logwrite(LOG_ALERT, "process got signal %d\n", WTERMSIG(status));
meillo@10 332 } else
meillo@10 333 ok = TRUE;
meillo@0 334
meillo@10 335 } else
meillo@10 336 logwrite(LOG_ALERT, "could not open pipe '%s': %s\n", cmd, strerror(errno));
meillo@0 337
meillo@10 338 signal(SIGCHLD, old_signal);
meillo@0 339
meillo@10 340 /* free environment */
meillo@10 341 for (i = 0; i < n; i++) {
meillo@10 342 g_free(envp[i]);
meillo@10 343 }
meillo@0 344
meillo@10 345 /* set uid and gid back */
meillo@10 346 if (!conf.run_as_user) {
meillo@10 347 set_euidgid(saved_uid, saved_gid, NULL, NULL);
meillo@10 348 }
meillo@0 349
meillo@10 350 return ok;
meillo@0 351 }