masqmail

annotate src/spool.c @ 281:ea5f86e0a81c

modes are now enforced exclusive Other MTAs (exim, postfix) are more relaxing, but as combinations of exclusive modes are senseless we behave more obvious if we fail early. This makes understanding the behavior easier too.
author markus schnalke <meillo@marmaro.de>
date Tue, 07 Dec 2010 14:04:56 -0300
parents 92063f90f9be
children d04894d0fc64
rev   line source
meillo@0 1 /* MasqMail
meillo@0 2 Copyright (C) 1999-2001 Oliver Kurth
meillo@76 3 Copyright (C) 2010 markus schnalke <meillo@marmaro.de>
meillo@0 4
meillo@0 5 This program is free software; you can redistribute it and/or modify
meillo@0 6 it under the terms of the GNU General Public License as published by
meillo@0 7 the Free Software Foundation; either version 2 of the License, or
meillo@0 8 (at your option) any later version.
meillo@0 9
meillo@0 10 This program is distributed in the hope that it will be useful,
meillo@0 11 but WITHOUT ANY WARRANTY; without even the implied warranty of
meillo@0 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
meillo@0 13 GNU General Public License for more details.
meillo@0 14
meillo@0 15 You should have received a copy of the GNU General Public License
meillo@0 16 along with this program; if not, write to the Free Software
meillo@0 17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
meillo@0 18 */
meillo@0 19
meillo@15 20 #include <sys/stat.h>
meillo@15 21
meillo@0 22 #include "masqmail.h"
meillo@0 23 #include "dotlock.h"
meillo@0 24
meillo@10 25 static gint
meillo@10 26 read_line(FILE * in, gchar * buf, gint buf_len)
meillo@0 27 {
meillo@10 28 gint p = 0;
meillo@10 29 gint c;
meillo@0 30
meillo@10 31 while ((c = getc(in)) != '\n' && (c != EOF)) {
meillo@10 32 if (p >= buf_len - 1) {
meillo@75 33 buf[buf_len-1] = '\0';
meillo@75 34 ungetc(c, in);
meillo@75 35 return buf_len;
meillo@10 36 }
meillo@10 37 buf[p++] = c;
meillo@10 38 }
meillo@0 39
meillo@10 40 if (c == EOF) {
meillo@10 41 return -1;
meillo@10 42 }
meillo@10 43 if ((p > 0) && (buf[p - 1] == '\r'))
meillo@10 44 p--;
meillo@10 45 buf[p++] = '\n';
meillo@15 46 buf[p] = '\0';
meillo@0 47
meillo@10 48 return p;
meillo@0 49 }
meillo@0 50
meillo@10 51 static void
meillo@10 52 spool_write_rcpt(FILE * out, address * rcpt)
meillo@0 53 {
meillo@10 54 gchar dlvrd_char = addr_is_delivered(rcpt) ? 'X' : (addr_is_failed(rcpt) ? 'F' : ' ');
meillo@0 55
meillo@10 56 if (rcpt->local_part[0] != '|') {
meillo@10 57 /* this is a paranoid check, in case it slipped through: */
meillo@10 58 /* if this happens, it is a bug */
meillo@10 59 if (rcpt->domain == NULL) {
meillo@10 60 logwrite(LOG_WARNING, "BUG: null domain for address %s, setting to %s\n", rcpt->local_part, conf.host_name);
meillo@10 61 logwrite(LOG_WARNING, "please report this bug.\n");
meillo@10 62 rcpt->domain = g_strdup(conf.host_name);
meillo@10 63 }
meillo@10 64 fprintf(out, "RT:%c%s\n", dlvrd_char, addr_string(rcpt));
meillo@10 65 } else {
meillo@10 66 fprintf(out, "RT:%c%s\n", dlvrd_char, rcpt->local_part);
meillo@10 67 }
meillo@0 68 }
meillo@0 69
meillo@10 70 static address*
meillo@10 71 spool_scan_rcpt(gchar * line)
meillo@0 72 {
meillo@10 73 address *rcpt = NULL;
meillo@0 74
meillo@15 75 if (line[3] != '\0') {
meillo@10 76 if (line[4] != '|') {
meillo@10 77 rcpt = create_address(&(line[4]), TRUE);
meillo@10 78 } else {
meillo@10 79 rcpt = create_address_pipe(&(line[4]));
meillo@10 80 }
meillo@10 81 if (line[3] == 'X') {
meillo@10 82 addr_mark_delivered(rcpt);
meillo@10 83 } else if (line[3] == 'F') {
meillo@10 84 addr_mark_failed(rcpt);
meillo@10 85 }
meillo@10 86 }
meillo@10 87 return rcpt;
meillo@0 88 }
meillo@0 89
meillo@10 90 gboolean
meillo@10 91 spool_read_data(message * msg)
meillo@0 92 {
meillo@10 93 FILE *in;
meillo@10 94 gchar *spool_file;
meillo@0 95
meillo@10 96 DEBUG(5) debugf("spool_read_data entered\n");
meillo@10 97 spool_file = g_strdup_printf("%s/input/%s-D", conf.spool_dir, msg->uid);
meillo@10 98 DEBUG(5) debugf("reading data spool file '%s'\n", spool_file);
meillo@82 99 in = fopen(spool_file, "r");
meillo@82 100 if (!in) {
meillo@82 101 logwrite(LOG_ALERT, "could not open spool data file %s: %s\n", spool_file, strerror(errno));
meillo@82 102 return FALSE;
meillo@82 103 }
meillo@10 104
meillo@82 105 char buf[MAX_DATALINE];
meillo@82 106 int len;
meillo@10 107
meillo@82 108 /* msg uid */
meillo@82 109 read_line(in, buf, MAX_DATALINE);
meillo@82 110
meillo@82 111 /* data */
meillo@82 112 msg->data_list = NULL;
meillo@82 113 while ((len = read_line(in, buf, MAX_DATALINE)) > 0) {
meillo@82 114 msg->data_list = g_list_prepend(msg->data_list, g_strdup(buf));
meillo@82 115 }
meillo@82 116 msg->data_list = g_list_reverse(msg->data_list);
meillo@82 117 fclose(in);
meillo@82 118 return TRUE;
meillo@0 119 }
meillo@0 120
meillo@10 121 gboolean
meillo@10 122 spool_read_header(message * msg)
meillo@0 123 {
meillo@10 124 FILE *in;
meillo@10 125 gchar *spool_file;
meillo@0 126
meillo@10 127 /* header spool: */
meillo@10 128 spool_file = g_strdup_printf("%s/input/%s-H", conf.spool_dir, msg->uid);
meillo@82 129 in = fopen(spool_file, "r");
meillo@82 130 if (!in) {
meillo@82 131 logwrite(LOG_ALERT, "could not open spool header file %s: %s\n",
meillo@82 132 spool_file, strerror(errno));
meillo@82 133 return FALSE;
meillo@82 134 }
meillo@0 135
meillo@82 136 header *hdr = NULL;
meillo@82 137 char buf[MAX_DATALINE];
meillo@82 138 int len;
meillo@10 139
meillo@82 140 /* msg uid */
meillo@82 141 read_line(in, buf, MAX_DATALINE);
meillo@82 142
meillo@82 143 /* envelope header */
meillo@82 144 while ((len = read_line(in, buf, MAX_DATALINE)) > 0) {
meillo@82 145 if (buf[0] == '\n') {
meillo@82 146 break;
meillo@82 147 } else if (strncasecmp(buf, "MF:", 3) == 0) {
meillo@82 148 msg->return_path = create_address(&(buf[3]), TRUE);
meillo@235 149 DEBUG(3) debugf("spool_read: MAIL FROM: %s", msg->return_path->address);
meillo@82 150 } else if (strncasecmp(buf, "RT:", 3) == 0) {
meillo@82 151 address *addr;
meillo@82 152 addr = spool_scan_rcpt(buf);
meillo@108 153 if (addr_is_delivered(addr) || addr_is_failed(addr)) {
meillo@108 154 msg->non_rcpt_list = g_list_append(msg->non_rcpt_list, addr);
meillo@108 155 } else {
meillo@82 156 msg->rcpt_list = g_list_append(msg->rcpt_list, addr);
meillo@82 157 }
meillo@82 158 } else if (strncasecmp(buf, "PR:", 3) == 0) {
meillo@82 159 prot_id i;
meillo@82 160 for (i = 0; i < PROT_NUM; i++) {
meillo@82 161 if (strncasecmp(prot_names[i], &(buf[3]), strlen(prot_names[i])) == 0) {
meillo@82 162 break;
meillo@10 163 }
meillo@10 164 }
meillo@82 165 msg->received_prot = i;
meillo@82 166 } else if (strncasecmp(buf, "RH:", 3) == 0) {
meillo@82 167 g_strchomp(buf);
meillo@82 168 msg->received_host = g_strdup(&(buf[3]));
meillo@82 169 } else if (strncasecmp(buf, "ID:", 3) == 0) {
meillo@82 170 g_strchomp(buf);
meillo@82 171 msg->ident = g_strdup(&(buf[3]));
meillo@82 172 } else if (strncasecmp(buf, "DS:", 3) == 0) {
meillo@82 173 msg->data_size = atoi(&(buf[3]));
meillo@82 174 } else if (strncasecmp(buf, "TR:", 3) == 0) {
meillo@82 175 msg->received_time = (time_t) (atoi(&(buf[3])));
meillo@82 176 } else if (strncasecmp(buf, "TW:", 3) == 0) {
meillo@82 177 msg->warned_time = (time_t) (atoi(&(buf[3])));
meillo@10 178 }
meillo@82 179 /* so far ignore other tags */
meillo@82 180 }
meillo@10 181
meillo@82 182 /* mail headers */
meillo@82 183 while ((len = read_line(in, buf, MAX_DATALINE)) > 0) {
meillo@82 184 if (strncasecmp(buf, "HD:", 3) == 0) {
meillo@82 185 hdr = get_header(&(buf[3]));
meillo@82 186 msg->hdr_list = g_list_append(msg->hdr_list, hdr);
meillo@82 187 } else if ((buf[0] == ' ' || buf[0] == '\t') && hdr) {
meillo@82 188 char *tmp = hdr->header;
meillo@82 189 /* header continuation */
meillo@82 190 hdr->header = g_strconcat(hdr->header, buf, NULL);
meillo@82 191 hdr->value = hdr->header + (hdr->value - tmp);
meillo@82 192 } else {
meillo@82 193 break;
meillo@10 194 }
meillo@82 195 }
meillo@82 196 fclose(in);
meillo@82 197 return TRUE;
meillo@0 198 }
meillo@0 199
meillo@10 200 message*
meillo@10 201 msg_spool_read(gchar * uid, gboolean do_readdata)
meillo@0 202 {
meillo@10 203 message *msg;
meillo@10 204 gboolean ok = FALSE;
meillo@0 205
meillo@10 206 msg = create_message();
meillo@10 207 msg->uid = g_strdup(uid);
meillo@10 208
meillo@114 209 DEBUG(4) debugf("msg_spool_read():\n");
meillo@10 210 /* header spool: */
meillo@10 211 ok = spool_read_header(msg);
meillo@236 212 DEBUG(4) debugf(" spool_read_header() returned: %d (do_readdata had been: %d)\n",
meillo@236 213 ok, do_readdata);
meillo@10 214 if (ok && do_readdata) {
meillo@10 215 /* data spool: */
meillo@10 216 ok = spool_read_data(msg);
meillo@236 217 DEBUG(4) debugf(" spool_read_data() returned: %d\n", ok);
meillo@10 218 }
meillo@10 219 return msg;
meillo@0 220 }
meillo@0 221
meillo@0 222 /* write header. uid and gid should already be set to the
meillo@0 223 mail ids. Better call spool_write(msg, FALSE).
meillo@0 224 */
meillo@10 225 static gboolean
meillo@10 226 spool_write_header(message * msg)
meillo@0 227 {
meillo@10 228 GList *node;
meillo@10 229 gchar *spool_file, *tmp_file;
meillo@10 230 FILE *out;
meillo@10 231 gboolean ok = TRUE;
meillo@0 232
meillo@10 233 /* header spool: */
meillo@10 234 tmp_file = g_strdup_printf("%s/input/%d-H.tmp", conf.spool_dir, getpid());
meillo@10 235 DEBUG(4) debugf("tmp_file = %s\n", tmp_file);
meillo@0 236
meillo@10 237 if ((out = fopen(tmp_file, "w"))) {
meillo@10 238 DEBUG(6) debugf("opened tmp_file %s\n", tmp_file);
meillo@0 239
meillo@10 240 fprintf(out, "%s\n", msg->uid);
meillo@10 241 fprintf(out, "MF:%s\n", addr_string(msg->return_path));
meillo@0 242
meillo@10 243 DEBUG(6) debugf("after MF\n");
meillo@10 244 foreach(msg->rcpt_list, node) {
meillo@10 245 address *rcpt = (address *) (node->data);
meillo@10 246 spool_write_rcpt(out, rcpt);
meillo@10 247 }
meillo@10 248 foreach(msg->non_rcpt_list, node) {
meillo@10 249 address *rcpt = (address *) (node->data);
meillo@10 250 spool_write_rcpt(out, rcpt);
meillo@10 251 }
meillo@10 252 DEBUG(6) debugf("after RT\n");
meillo@10 253 fprintf(out, "PR:%s\n", prot_names[msg->received_prot]);
meillo@10 254 if (msg->received_host != NULL)
meillo@10 255 fprintf(out, "RH:%s\n", msg->received_host);
meillo@0 256
meillo@10 257 if (msg->ident != NULL)
meillo@10 258 fprintf(out, "ID:%s\n", msg->ident);
meillo@0 259
meillo@10 260 if (msg->data_size >= 0)
meillo@10 261 fprintf(out, "DS: %d\n", msg->data_size);
meillo@0 262
meillo@10 263 if (msg->received_time > 0)
meillo@10 264 fprintf(out, "TR: %u\n", (int) (msg->received_time));
meillo@0 265
meillo@10 266 if (msg->warned_time > 0)
meillo@10 267 fprintf(out, "TW: %u\n", (int) (msg->warned_time));
meillo@0 268
meillo@10 269 DEBUG(6) debugf("after RH\n");
meillo@10 270 fprintf(out, "\n");
meillo@0 271
meillo@10 272 foreach(msg->hdr_list, node) {
meillo@10 273 header *hdr = (header *) (node->data);
meillo@10 274 fprintf(out, "HD:%s", hdr->header);
meillo@10 275 }
meillo@10 276 if (fflush(out) == EOF)
meillo@10 277 ok = FALSE;
meillo@10 278 else if (fdatasync(fileno(out)) != 0) {
meillo@10 279 if (errno != EINVAL) /* some fs do not support this.. I hope this also means that it is not necessary */
meillo@10 280 ok = FALSE;
meillo@10 281 }
meillo@10 282 fclose(out);
meillo@10 283 if (ok) {
meillo@10 284 spool_file = g_strdup_printf("%s/input/%s-H", conf.spool_dir, msg->uid);
meillo@10 285 DEBUG(4) debugf("spool_file = %s\n", spool_file);
meillo@10 286 ok = (rename(tmp_file, spool_file) != -1);
meillo@10 287 g_free(spool_file);
meillo@10 288 }
meillo@10 289 } else {
meillo@10 290 logwrite(LOG_ALERT, "could not open temporary header spool file '%s': %s\n", tmp_file, strerror(errno));
meillo@10 291 DEBUG(1) debugf("euid = %d, egid = %d\n", geteuid(), getegid());
meillo@10 292 ok = FALSE;
meillo@10 293 }
meillo@0 294
meillo@10 295 g_free(tmp_file);
meillo@0 296
meillo@10 297 return ok;
meillo@0 298 }
meillo@0 299
meillo@10 300 gboolean
meillo@10 301 spool_write(message * msg, gboolean do_write_data)
meillo@0 302 {
meillo@10 303 GList *list;
meillo@10 304 gchar *spool_file, *tmp_file;
meillo@10 305 FILE *out;
meillo@10 306 gboolean ok = TRUE;
meillo@10 307 uid_t saved_uid, saved_gid;
meillo@10 308 /* user can read/write, group can read, others cannot do anything: */
meillo@10 309 mode_t saved_mode = saved_mode = umask(026);
meillo@0 310
meillo@10 311 /* set uid and gid to the mail ids */
meillo@10 312 if (!conf.run_as_user) {
meillo@10 313 set_euidgid(conf.mail_uid, conf.mail_gid, &saved_uid, &saved_gid);
meillo@0 314 }
meillo@0 315
meillo@10 316 /* header spool: */
meillo@10 317 ok = spool_write_header(msg);
meillo@10 318
meillo@82 319 if (ok && do_write_data) {
meillo@82 320 /* data spool: */
meillo@82 321 tmp_file = g_strdup_printf("%s/input/%d-D.tmp", conf.spool_dir, getpid());
meillo@82 322 DEBUG(4) debugf("tmp_file = %s\n", tmp_file);
meillo@10 323
meillo@82 324 if ((out = fopen(tmp_file, "w"))) {
meillo@82 325 fprintf(out, "%s\n", msg->uid);
meillo@82 326 for (list = g_list_first(msg->data_list); list != NULL; list = g_list_next(list)) {
meillo@82 327 fprintf(out, "%s", (gchar *) (list->data));
meillo@82 328 }
meillo@10 329
meillo@82 330 /* possibly paranoid ;-) */
meillo@82 331 if (fflush(out) == EOF) {
meillo@82 332 ok = FALSE;
meillo@82 333 } else if (fdatasync(fileno(out)) != 0) {
meillo@82 334 if (errno != EINVAL) { /* some fs do not support this.. I hope this also means that it is not necessary */
meillo@82 335 ok = FALSE;
meillo@10 336 }
meillo@10 337 }
meillo@82 338 fclose(out);
meillo@82 339 if (ok) {
meillo@82 340 spool_file = g_strdup_printf("%s/input/%s-D", conf.spool_dir, msg->uid);
meillo@82 341 DEBUG(4) debugf("spool_file = %s\n", spool_file);
meillo@82 342 ok = (rename(tmp_file, spool_file) != -1);
meillo@82 343 g_free(spool_file);
meillo@82 344 }
meillo@82 345 } else {
meillo@82 346 logwrite(LOG_ALERT, "could not open temporary data spool file: %s\n",
meillo@82 347 strerror(errno));
meillo@82 348 ok = FALSE;
meillo@10 349 }
meillo@82 350 g_free(tmp_file);
meillo@0 351 }
meillo@10 352
meillo@10 353 /* set uid and gid back */
meillo@10 354 if (!conf.run_as_user) {
meillo@10 355 set_euidgid(saved_uid, saved_gid, NULL, NULL);
meillo@0 356 }
meillo@0 357
meillo@10 358 umask(saved_mode);
meillo@0 359
meillo@10 360 return ok;
meillo@0 361 }
meillo@0 362
meillo@0 363 #define MAX_LOCKAGE 300
meillo@0 364
meillo@10 365 gboolean
meillo@10 366 spool_lock(gchar * uid)
meillo@0 367 {
meillo@10 368 uid_t saved_uid, saved_gid;
meillo@10 369 gchar *hitch_name;
meillo@10 370 gchar *lock_name;
meillo@10 371 gboolean ok = FALSE;
meillo@0 372
meillo@10 373 hitch_name = g_strdup_printf("%s/%s-%d.lock", conf.lock_dir, uid, getpid());
meillo@10 374 lock_name = g_strdup_printf("%s/%s.lock", conf.lock_dir, uid);
meillo@0 375
meillo@10 376 /* set uid and gid to the mail ids */
meillo@10 377 if (!conf.run_as_user) {
meillo@10 378 set_euidgid(conf.mail_uid, conf.mail_gid, &saved_uid, &saved_gid);
meillo@10 379 }
meillo@0 380
meillo@10 381 ok = dot_lock(lock_name, hitch_name);
meillo@10 382 if (!ok)
meillo@10 383 logwrite(LOG_WARNING, "spool file %s is locked\n", uid);
meillo@0 384
meillo@10 385 /* set uid and gid back */
meillo@10 386 if (!conf.run_as_user) {
meillo@10 387 set_euidgid(saved_uid, saved_gid, NULL, NULL);
meillo@10 388 }
meillo@0 389
meillo@10 390 g_free(lock_name);
meillo@10 391 g_free(hitch_name);
meillo@0 392
meillo@10 393 return ok;
meillo@0 394 }
meillo@0 395
meillo@10 396 gboolean
meillo@10 397 spool_unlock(gchar * uid)
meillo@0 398 {
meillo@10 399 uid_t saved_uid, saved_gid;
meillo@10 400 gchar *lock_name;
meillo@0 401
meillo@10 402 /* set uid and gid to the mail ids */
meillo@10 403 if (!conf.run_as_user) {
meillo@10 404 set_euidgid(conf.mail_uid, conf.mail_gid, &saved_uid, &saved_gid);
meillo@10 405 }
meillo@0 406
meillo@10 407 lock_name = g_strdup_printf("%s/%s.lock", conf.lock_dir, uid);
meillo@10 408 dot_unlock(lock_name);
meillo@10 409 g_free(lock_name);
meillo@0 410
meillo@10 411 /* set uid and gid back */
meillo@10 412 if (!conf.run_as_user) {
meillo@10 413 set_euidgid(saved_uid, saved_gid, NULL, NULL);
meillo@10 414 }
meillo@10 415 return TRUE;
meillo@0 416 }
meillo@0 417
meillo@10 418 gboolean
meillo@10 419 spool_delete_all(message * msg)
meillo@0 420 {
meillo@10 421 uid_t saved_uid, saved_gid;
meillo@10 422 gchar *spool_file;
meillo@0 423
meillo@10 424 /* set uid and gid to the mail ids */
meillo@10 425 if (!conf.run_as_user) {
meillo@10 426 set_euidgid(conf.mail_uid, conf.mail_gid, &saved_uid, &saved_gid);
meillo@10 427 }
meillo@0 428
meillo@10 429 /* header spool: */
meillo@10 430 spool_file = g_strdup_printf("%s/input/%s-H", conf.spool_dir, msg->uid);
meillo@82 431 if (unlink(spool_file) != 0) {
meillo@10 432 logwrite(LOG_ALERT, "could not delete spool file %s: %s\n", spool_file, strerror(errno));
meillo@82 433 }
meillo@10 434 g_free(spool_file);
meillo@0 435
meillo@10 436 /* data spool: */
meillo@10 437 spool_file = g_strdup_printf("%s/input/%s-D", conf.spool_dir, msg->uid);
meillo@82 438 if (unlink(spool_file) != 0) {
meillo@10 439 logwrite(LOG_ALERT, "could not delete spool file %s: %s\n", spool_file, strerror(errno));
meillo@82 440 }
meillo@10 441 g_free(spool_file);
meillo@0 442
meillo@10 443 /* set uid and gid back */
meillo@10 444 if (!conf.run_as_user) {
meillo@10 445 set_euidgid(saved_uid, saved_gid, NULL, NULL);
meillo@10 446 }
meillo@10 447 return TRUE;
meillo@0 448 }