masqmail-0.2

annotate src/spool.c @ 179:ec3fe72a3e99

Fixed an important bug with folded headers! g_strconcat() returns a *copy* of the string, but hdr->value still pointed to the old header (which probably was a memory leak, too). If the folded part had been quite small it was likely that the new string was at the same position as the old one, thus making everything go well. But if pretty long headers were folded several times it was likely that the new string was allocated somewhere else in memory, thus breaking things. In result mails to lots of recipients (folded header) were frequently only sent to the ones in the first line. Sorry for the inconvenience.
author meillo@marmaro.de
date Fri, 03 Jun 2011 09:52:17 +0200
parents 46f407c0727a
children
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@82 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@114 212 DEBUG(4) debugf(" spool_read_header()=%d, do_readdata=%d\n", ok, do_readdata);
meillo@10 213 if (ok && do_readdata) {
meillo@10 214 /* data spool: */
meillo@10 215 ok = spool_read_data(msg);
meillo@114 216 DEBUG(4) debugf(" spool_read_data()=%d\n", ok);
meillo@10 217 }
meillo@10 218 return msg;
meillo@0 219 }
meillo@0 220
meillo@0 221 /* write header. uid and gid should already be set to the
meillo@0 222 mail ids. Better call spool_write(msg, FALSE).
meillo@0 223 */
meillo@10 224 static gboolean
meillo@10 225 spool_write_header(message * msg)
meillo@0 226 {
meillo@10 227 GList *node;
meillo@10 228 gchar *spool_file, *tmp_file;
meillo@10 229 FILE *out;
meillo@10 230 gboolean ok = TRUE;
meillo@0 231
meillo@10 232 /* header spool: */
meillo@10 233 tmp_file = g_strdup_printf("%s/input/%d-H.tmp", conf.spool_dir, getpid());
meillo@10 234 DEBUG(4) debugf("tmp_file = %s\n", tmp_file);
meillo@0 235
meillo@10 236 if ((out = fopen(tmp_file, "w"))) {
meillo@10 237 DEBUG(6) debugf("opened tmp_file %s\n", tmp_file);
meillo@0 238
meillo@10 239 fprintf(out, "%s\n", msg->uid);
meillo@10 240 fprintf(out, "MF:%s\n", addr_string(msg->return_path));
meillo@0 241
meillo@10 242 DEBUG(6) debugf("after MF\n");
meillo@10 243 foreach(msg->rcpt_list, node) {
meillo@10 244 address *rcpt = (address *) (node->data);
meillo@10 245 spool_write_rcpt(out, rcpt);
meillo@10 246 }
meillo@10 247 foreach(msg->non_rcpt_list, node) {
meillo@10 248 address *rcpt = (address *) (node->data);
meillo@10 249 spool_write_rcpt(out, rcpt);
meillo@10 250 }
meillo@10 251 DEBUG(6) debugf("after RT\n");
meillo@10 252 fprintf(out, "PR:%s\n", prot_names[msg->received_prot]);
meillo@10 253 if (msg->received_host != NULL)
meillo@10 254 fprintf(out, "RH:%s\n", msg->received_host);
meillo@0 255
meillo@10 256 if (msg->ident != NULL)
meillo@10 257 fprintf(out, "ID:%s\n", msg->ident);
meillo@0 258
meillo@10 259 if (msg->data_size >= 0)
meillo@10 260 fprintf(out, "DS: %d\n", msg->data_size);
meillo@0 261
meillo@10 262 if (msg->received_time > 0)
meillo@10 263 fprintf(out, "TR: %u\n", (int) (msg->received_time));
meillo@0 264
meillo@10 265 if (msg->warned_time > 0)
meillo@10 266 fprintf(out, "TW: %u\n", (int) (msg->warned_time));
meillo@0 267
meillo@10 268 DEBUG(6) debugf("after RH\n");
meillo@10 269 fprintf(out, "\n");
meillo@0 270
meillo@10 271 foreach(msg->hdr_list, node) {
meillo@10 272 header *hdr = (header *) (node->data);
meillo@10 273 fprintf(out, "HD:%s", hdr->header);
meillo@10 274 }
meillo@10 275 if (fflush(out) == EOF)
meillo@10 276 ok = FALSE;
meillo@10 277 else if (fdatasync(fileno(out)) != 0) {
meillo@10 278 if (errno != EINVAL) /* some fs do not support this.. I hope this also means that it is not necessary */
meillo@10 279 ok = FALSE;
meillo@10 280 }
meillo@10 281 fclose(out);
meillo@10 282 if (ok) {
meillo@10 283 spool_file = g_strdup_printf("%s/input/%s-H", conf.spool_dir, msg->uid);
meillo@10 284 DEBUG(4) debugf("spool_file = %s\n", spool_file);
meillo@10 285 ok = (rename(tmp_file, spool_file) != -1);
meillo@10 286 g_free(spool_file);
meillo@10 287 }
meillo@10 288 } else {
meillo@10 289 logwrite(LOG_ALERT, "could not open temporary header spool file '%s': %s\n", tmp_file, strerror(errno));
meillo@10 290 DEBUG(1) debugf("euid = %d, egid = %d\n", geteuid(), getegid());
meillo@10 291 ok = FALSE;
meillo@10 292 }
meillo@0 293
meillo@10 294 g_free(tmp_file);
meillo@0 295
meillo@10 296 return ok;
meillo@0 297 }
meillo@0 298
meillo@10 299 gboolean
meillo@10 300 spool_write(message * msg, gboolean do_write_data)
meillo@0 301 {
meillo@10 302 GList *list;
meillo@10 303 gchar *spool_file, *tmp_file;
meillo@10 304 FILE *out;
meillo@10 305 gboolean ok = TRUE;
meillo@10 306 uid_t saved_uid, saved_gid;
meillo@10 307 /* user can read/write, group can read, others cannot do anything: */
meillo@10 308 mode_t saved_mode = saved_mode = umask(026);
meillo@0 309
meillo@10 310 /* set uid and gid to the mail ids */
meillo@10 311 if (!conf.run_as_user) {
meillo@10 312 set_euidgid(conf.mail_uid, conf.mail_gid, &saved_uid, &saved_gid);
meillo@0 313 }
meillo@0 314
meillo@10 315 /* header spool: */
meillo@10 316 ok = spool_write_header(msg);
meillo@10 317
meillo@82 318 if (ok && do_write_data) {
meillo@82 319 /* data spool: */
meillo@82 320 tmp_file = g_strdup_printf("%s/input/%d-D.tmp", conf.spool_dir, getpid());
meillo@82 321 DEBUG(4) debugf("tmp_file = %s\n", tmp_file);
meillo@10 322
meillo@82 323 if ((out = fopen(tmp_file, "w"))) {
meillo@82 324 fprintf(out, "%s\n", msg->uid);
meillo@82 325 for (list = g_list_first(msg->data_list); list != NULL; list = g_list_next(list)) {
meillo@82 326 fprintf(out, "%s", (gchar *) (list->data));
meillo@82 327 }
meillo@10 328
meillo@82 329 /* possibly paranoid ;-) */
meillo@82 330 if (fflush(out) == EOF) {
meillo@82 331 ok = FALSE;
meillo@82 332 } else if (fdatasync(fileno(out)) != 0) {
meillo@82 333 if (errno != EINVAL) { /* some fs do not support this.. I hope this also means that it is not necessary */
meillo@82 334 ok = FALSE;
meillo@10 335 }
meillo@10 336 }
meillo@82 337 fclose(out);
meillo@82 338 if (ok) {
meillo@82 339 spool_file = g_strdup_printf("%s/input/%s-D", conf.spool_dir, msg->uid);
meillo@82 340 DEBUG(4) debugf("spool_file = %s\n", spool_file);
meillo@82 341 ok = (rename(tmp_file, spool_file) != -1);
meillo@82 342 g_free(spool_file);
meillo@82 343 }
meillo@82 344 } else {
meillo@82 345 logwrite(LOG_ALERT, "could not open temporary data spool file: %s\n",
meillo@82 346 strerror(errno));
meillo@82 347 ok = FALSE;
meillo@10 348 }
meillo@82 349 g_free(tmp_file);
meillo@0 350 }
meillo@10 351
meillo@10 352 /* set uid and gid back */
meillo@10 353 if (!conf.run_as_user) {
meillo@10 354 set_euidgid(saved_uid, saved_gid, NULL, NULL);
meillo@0 355 }
meillo@0 356
meillo@10 357 umask(saved_mode);
meillo@0 358
meillo@10 359 return ok;
meillo@0 360 }
meillo@0 361
meillo@0 362 #define MAX_LOCKAGE 300
meillo@0 363
meillo@10 364 gboolean
meillo@10 365 spool_lock(gchar * uid)
meillo@0 366 {
meillo@10 367 uid_t saved_uid, saved_gid;
meillo@10 368 gchar *hitch_name;
meillo@10 369 gchar *lock_name;
meillo@10 370 gboolean ok = FALSE;
meillo@0 371
meillo@10 372 hitch_name = g_strdup_printf("%s/%s-%d.lock", conf.lock_dir, uid, getpid());
meillo@10 373 lock_name = g_strdup_printf("%s/%s.lock", conf.lock_dir, uid);
meillo@0 374
meillo@10 375 /* set uid and gid to the mail ids */
meillo@10 376 if (!conf.run_as_user) {
meillo@10 377 set_euidgid(conf.mail_uid, conf.mail_gid, &saved_uid, &saved_gid);
meillo@10 378 }
meillo@0 379
meillo@10 380 ok = dot_lock(lock_name, hitch_name);
meillo@10 381 if (!ok)
meillo@10 382 logwrite(LOG_WARNING, "spool file %s is locked\n", uid);
meillo@0 383
meillo@10 384 /* set uid and gid back */
meillo@10 385 if (!conf.run_as_user) {
meillo@10 386 set_euidgid(saved_uid, saved_gid, NULL, NULL);
meillo@10 387 }
meillo@0 388
meillo@10 389 g_free(lock_name);
meillo@10 390 g_free(hitch_name);
meillo@0 391
meillo@10 392 return ok;
meillo@0 393 }
meillo@0 394
meillo@10 395 gboolean
meillo@10 396 spool_unlock(gchar * uid)
meillo@0 397 {
meillo@10 398 uid_t saved_uid, saved_gid;
meillo@10 399 gchar *lock_name;
meillo@0 400
meillo@10 401 /* set uid and gid to the mail ids */
meillo@10 402 if (!conf.run_as_user) {
meillo@10 403 set_euidgid(conf.mail_uid, conf.mail_gid, &saved_uid, &saved_gid);
meillo@10 404 }
meillo@0 405
meillo@10 406 lock_name = g_strdup_printf("%s/%s.lock", conf.lock_dir, uid);
meillo@10 407 dot_unlock(lock_name);
meillo@10 408 g_free(lock_name);
meillo@0 409
meillo@10 410 /* set uid and gid back */
meillo@10 411 if (!conf.run_as_user) {
meillo@10 412 set_euidgid(saved_uid, saved_gid, NULL, NULL);
meillo@10 413 }
meillo@10 414 return TRUE;
meillo@0 415 }
meillo@0 416
meillo@10 417 gboolean
meillo@10 418 spool_delete_all(message * msg)
meillo@0 419 {
meillo@10 420 uid_t saved_uid, saved_gid;
meillo@10 421 gchar *spool_file;
meillo@0 422
meillo@10 423 /* set uid and gid to the mail ids */
meillo@10 424 if (!conf.run_as_user) {
meillo@10 425 set_euidgid(conf.mail_uid, conf.mail_gid, &saved_uid, &saved_gid);
meillo@10 426 }
meillo@0 427
meillo@10 428 /* header spool: */
meillo@10 429 spool_file = g_strdup_printf("%s/input/%s-H", conf.spool_dir, msg->uid);
meillo@82 430 if (unlink(spool_file) != 0) {
meillo@10 431 logwrite(LOG_ALERT, "could not delete spool file %s: %s\n", spool_file, strerror(errno));
meillo@82 432 }
meillo@10 433 g_free(spool_file);
meillo@0 434
meillo@10 435 /* data spool: */
meillo@10 436 spool_file = g_strdup_printf("%s/input/%s-D", conf.spool_dir, msg->uid);
meillo@82 437 if (unlink(spool_file) != 0) {
meillo@10 438 logwrite(LOG_ALERT, "could not delete spool file %s: %s\n", spool_file, strerror(errno));
meillo@82 439 }
meillo@10 440 g_free(spool_file);
meillo@0 441
meillo@10 442 /* set uid and gid back */
meillo@10 443 if (!conf.run_as_user) {
meillo@10 444 set_euidgid(saved_uid, saved_gid, NULL, NULL);
meillo@10 445 }
meillo@10 446 return TRUE;
meillo@0 447 }