masqmail-0.2

annotate src/local.c @ 171:349518b940db

added support for STARTTLS wrappers added the route config option `instant_helo' which causes masqmail, as SMTP client, not to wait for the server's 220 greeting. Instead if says EHLO right at once. You'll need this for STARTTLS wrappers that usually eat the greeting line.
author meillo@marmaro.de
date Thu, 22 Jul 2010 23:30:05 +0200
parents a80ebfa16cd5
children
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@152 20 #include <sys/stat.h>
meillo@15 21
meillo@0 22 #include "masqmail.h"
meillo@0 23 #include "peopen.h"
meillo@0 24
meillo@10 25 static void
meillo@10 26 message_stream(FILE * out, message * msg, GList * hdr_list, guint flags)
meillo@0 27 {
meillo@10 28 time_t now = time(NULL);
meillo@10 29 GList *node;
meillo@10 30
meillo@10 31 if (flags & MSGSTR_FROMLINE) {
meillo@10 32 fprintf(out, "From <%s@%s> %s", msg->return_path->local_part, msg->return_path->domain, ctime(&now));
meillo@10 33 }
meillo@10 34
meillo@10 35 foreach(hdr_list, node) {
meillo@10 36 header *hdr = (header *) (node->data);
meillo@10 37 fputs(hdr->header, out);
meillo@10 38 }
meillo@10 39 putc('\n', out);
meillo@10 40 foreach(msg->data_list, node) {
meillo@10 41 /* From hack: */
meillo@10 42 if (flags & MSGSTR_FROMHACK) {
meillo@10 43 if (strncmp(node->data, "From ", 5) == 0)
meillo@10 44 putc('>', out);
meillo@10 45 }
meillo@10 46 fputs(node->data, out);
meillo@10 47 }
meillo@10 48 putc('\n', out);
meillo@0 49 }
meillo@0 50
meillo@10 51 gboolean
meillo@10 52 append_file(message * msg, GList * hdr_list, gchar * user)
meillo@0 53 {
meillo@10 54 struct passwd *pw;
meillo@10 55 gboolean ok = FALSE;
meillo@0 56
meillo@10 57 /* headers may be special for a local delivery */
meillo@10 58 if (hdr_list == NULL)
meillo@10 59 hdr_list = msg->hdr_list;
meillo@0 60
meillo@10 61 if ((pw = getpwnam(user))) {
meillo@10 62 uid_t saved_uid = geteuid();
meillo@10 63 gid_t saved_gid = getegid();
meillo@10 64 gboolean uid_ok = TRUE, gid_ok = TRUE;
meillo@0 65
meillo@10 66 if (!conf.run_as_user) {
meillo@10 67 uid_ok = (seteuid(0) == 0);
meillo@10 68 if (uid_ok) {
meillo@10 69 gid_ok = (setegid(conf.mail_gid) == 0);
meillo@10 70 uid_ok = (seteuid(pw->pw_uid) == 0);
meillo@10 71 }
meillo@10 72 }
meillo@0 73
meillo@114 74 DEBUG(5) debugf("running as euid %d, egid %d\n", geteuid(), 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@114 171 DEBUG(5) debugf("running as euid %d, egid %d\n", geteuid(), getegid());
meillo@0 172
meillo@10 173 if (uid_ok && gid_ok) {
meillo@10 174 char *path = g_strdup_printf("%s/Maildir", pw->pw_dir);
meillo@10 175 struct stat statbuf;
meillo@10 176 int ret;
meillo@0 177
meillo@114 178 DEBUG(5) debugf(" path = %s\n", path);
meillo@10 179
meillo@10 180 ok = TRUE;
meillo@10 181 ret = stat(path, &statbuf);
meillo@10 182 if (ret != 0) {
meillo@10 183 ok = FALSE;
meillo@10 184 if (errno == ENOENT) {
meillo@10 185 logwrite(LOG_NOTICE, "directory %s does not exist, creating\n", path);
meillo@10 186 if (mkdir(path, 0700) == 0)
meillo@10 187 ok = TRUE;
meillo@10 188 } else
meillo@10 189 logwrite(LOG_ALERT, "stat of %s failed: %s\n", path, strerror(errno));
meillo@10 190 }
meillo@10 191 if (ok) {
meillo@10 192 ok = FALSE;
meillo@10 193 ret = stat(path, &statbuf);
meillo@10 194 if (S_ISDIR(statbuf.st_mode)) {
meillo@10 195 gchar *subdirs[] = { "tmp", "new", "cur" };
meillo@10 196 int i;
meillo@10 197 for (i = 0; i < 3; i++) {
meillo@10 198 char *path1 = g_strdup_printf("%s/%s", path, subdirs[i]);
meillo@10 199 ret = stat(path1, &statbuf);
meillo@10 200 if (ret != 0) {
meillo@10 201 if (errno == ENOENT) {
meillo@10 202 logwrite(LOG_NOTICE, "directory %s does not exist, creating\n", path1);
meillo@10 203 if (mkdir(path1, 0700) != 0)
meillo@10 204 break;
meillo@10 205 }
meillo@10 206 }
meillo@10 207 g_free(path1);
meillo@10 208 }
meillo@10 209 if (i == 3) {
meillo@10 210 FILE *out;
meillo@10 211 mode_t saved_mode = umask(066);
meillo@15 212 /* the qmail style unique works only if delivering with different process.
meillo@15 213 We do not fork for each delivery, so our uid is more unique.
meillo@15 214 Hope it is compatible with all MUAs.
meillo@10 215 */
meillo@10 216 gchar *filename = g_strdup_printf("%s/tmp/%s.%s", path, msg->uid, conf.host_name);
meillo@10 217
meillo@10 218 DEBUG(5) debugf("filename = %s\n", filename);
meillo@10 219
meillo@10 220 if ((out = fopen(filename, "w"))) {
meillo@10 221 gchar *newname = g_strdup_printf("%s/new/%s.%s", path, msg->uid, conf.host_name);
meillo@10 222 message_stream(out, msg, hdr_list, flags);
meillo@10 223 ok = TRUE;
meillo@10 224 if (fflush(out) == EOF)
meillo@10 225 ok = FALSE;
meillo@10 226 else if (fdatasync(fileno(out)) != 0) {
meillo@15 227 if (errno != EINVAL)
meillo@15 228 /* some fs do not support this.. I hope this also means that it is not necessary */
meillo@10 229 ok = FALSE;
meillo@10 230 }
meillo@10 231 fclose(out);
meillo@10 232 if (rename(filename, newname) != 0) {
meillo@10 233 ok = FALSE;
meillo@10 234 logwrite(LOG_ALERT, "moving %s to %s failed: %s", filename, newname, strerror(errno));
meillo@10 235 }
meillo@10 236 g_free(newname);
meillo@10 237 }
meillo@10 238 umask(saved_mode);
meillo@10 239 g_free(filename);
meillo@10 240 }
meillo@10 241 } else {
meillo@10 242 logwrite(LOG_ALERT, "%s is not a directory\n", path);
meillo@10 243 errno = ENOTDIR;
meillo@10 244 }
meillo@10 245 }
meillo@10 246 if (!conf.run_as_user) {
meillo@10 247 uid_ok = (seteuid(0) == 0);
meillo@10 248 if (uid_ok) {
meillo@10 249 gid_ok = (setegid(saved_gid) == 0);
meillo@10 250 uid_ok = (seteuid(saved_uid) == 0);
meillo@10 251 }
meillo@10 252 }
meillo@10 253 if (!uid_ok || !gid_ok) {
meillo@10 254 /* FIXME: if this fails we HAVE to exit, because we shall not run
meillo@10 255 with some users id. But we do not return, and so this message
meillo@10 256 will not be finished, so the user will get the message again
meillo@10 257 next time a delivery is attempted... */
meillo@10 258 logwrite(LOG_ALERT, "could not set back uid or gid after local delivery: %s\n", strerror(errno));
meillo@10 259 exit(EXIT_FAILURE);
meillo@10 260 }
meillo@10 261 g_free(path);
meillo@10 262 } else {
meillo@10 263 logwrite(LOG_ALERT, "could not set uid or gid for local delivery, uid = %d: %s\n", pw->pw_uid, strerror(errno));
meillo@10 264 }
meillo@10 265 } else {
meillo@10 266 logwrite(LOG_ALERT, "could not find password entry for user %s\n", user);
meillo@10 267 errno = ENOENT; /* getpwnam does not set errno correctly */
meillo@0 268 }
meillo@10 269 return ok;
meillo@0 270 }
meillo@0 271 #endif
meillo@0 272
meillo@0 273 gboolean
meillo@10 274 pipe_out(message * msg, GList * hdr_list, address * rcpt, gchar * cmd, guint flags)
meillo@0 275 {
meillo@10 276 gchar *envp[40];
meillo@10 277 FILE *out;
meillo@10 278 uid_t saved_uid = geteuid();
meillo@10 279 gid_t saved_gid = getegid();
meillo@10 280 gboolean ok = FALSE;
meillo@10 281 gint i, n;
meillo@10 282 pid_t pid;
meillo@10 283 void (*old_signal) (int);
meillo@10 284 int status;
meillo@0 285
meillo@10 286 /* set uid and gid to the mail ids */
meillo@10 287 if (!conf.run_as_user) {
meillo@10 288 set_euidgid(conf.mail_uid, conf.mail_gid, &saved_uid, &saved_gid);
meillo@10 289 }
meillo@0 290
meillo@10 291 /* set environment */
meillo@10 292 {
meillo@10 293 gint i = 0;
meillo@10 294 address *ancestor = addr_find_ancestor(rcpt);
meillo@0 295
meillo@10 296 envp[i++] = g_strdup_printf("SENDER=%s@%s", msg->return_path->local_part, msg->return_path->domain);
meillo@10 297 envp[i++] = g_strdup_printf("SENDER_DOMAIN=%s", msg->return_path->domain);
meillo@10 298 envp[i++] = g_strdup_printf("SENDER_LOCAL=%s", msg->return_path->local_part);
meillo@10 299 envp[i++] = g_strdup_printf("RECEIVED_HOST=%s", msg->received_host ? msg->received_host : "");
meillo@0 300
meillo@10 301 envp[i++] = g_strdup_printf("RETURN_PATH=%s@%s", msg->return_path->local_part, msg->return_path->domain);
meillo@10 302 envp[i++] = g_strdup_printf("DOMAIN=%s", ancestor->domain);
meillo@0 303
meillo@10 304 envp[i++] = g_strdup_printf("LOCAL_PART=%s", ancestor->local_part);
meillo@10 305 envp[i++] = g_strdup_printf("USER=%s", ancestor->local_part);
meillo@10 306 envp[i++] = g_strdup_printf("LOGNAME=%s", ancestor->local_part);
meillo@0 307
meillo@10 308 envp[i++] = g_strdup_printf("MESSAGE_ID=%s", msg->uid);
meillo@10 309 envp[i++] = g_strdup_printf("QUALIFY_DOMAIN=%s", conf.host_name);
meillo@0 310
meillo@10 311 envp[i] = NULL;
meillo@10 312 n = i;
meillo@10 313 }
meillo@0 314
meillo@10 315 old_signal = signal(SIGCHLD, SIG_DFL);
meillo@0 316
meillo@10 317 out = peidopen(cmd, "w", envp, &pid, conf.mail_uid, conf.mail_gid);
meillo@10 318 if (out != NULL) {
meillo@10 319 message_stream(out, msg, hdr_list, flags);
meillo@0 320
meillo@10 321 fclose(out);
meillo@0 322
meillo@10 323 waitpid(pid, &status, 0);
meillo@0 324
meillo@10 325 if (WEXITSTATUS(status) != 0) {
meillo@10 326 int exstat = WEXITSTATUS(status);
meillo@10 327 logwrite(LOG_ALERT, "process returned %d (%s)\n", exstat, ext_strerror(1024 + exstat));
meillo@10 328 errno = 1024 + exstat;
meillo@10 329 } else if (WIFSIGNALED(status)) {
meillo@10 330 logwrite(LOG_ALERT, "process got signal %d\n", WTERMSIG(status));
meillo@10 331 } else
meillo@10 332 ok = TRUE;
meillo@0 333
meillo@10 334 } else
meillo@10 335 logwrite(LOG_ALERT, "could not open pipe '%s': %s\n", cmd, strerror(errno));
meillo@0 336
meillo@10 337 signal(SIGCHLD, old_signal);
meillo@0 338
meillo@10 339 /* free environment */
meillo@10 340 for (i = 0; i < n; i++) {
meillo@10 341 g_free(envp[i]);
meillo@10 342 }
meillo@0 343
meillo@10 344 /* set uid and gid back */
meillo@10 345 if (!conf.run_as_user) {
meillo@10 346 set_euidgid(saved_uid, saved_gid, NULL, NULL);
meillo@10 347 }
meillo@0 348
meillo@10 349 return ok;
meillo@0 350 }