masqmail-0.2

annotate src/accept.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 087e99c7702a
children
rev   line source
meillo@0 1 /* MasqMail
meillo@0 2 Copyright (C) 1999-2001 Oliver Kurth
meillo@174 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@0 20 #include "masqmail.h"
meillo@0 21 #include "readsock.h"
meillo@0 22
meillo@10 23 gchar *prot_names[] = {
meillo@10 24 "local",
meillo@10 25 "bsmtp",
meillo@10 26 "smtp",
meillo@10 27 "esmtp",
meillo@10 28 "pop3",
meillo@10 29 "apop",
meillo@10 30 "(unknown)" /* should not happen, but better than crashing. */
meillo@0 31 };
meillo@0 32
meillo@10 33 static gchar*
meillo@10 34 string_base62(gchar * res, guint value, gchar len)
meillo@0 35 {
meillo@10 36 static gchar base62_chars[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
meillo@10 37 gchar *p = res + len;
meillo@14 38 *p = '\0';
meillo@10 39 while (p > res) {
meillo@10 40 *(--p) = base62_chars[value % 62];
meillo@10 41 value /= 62;
meillo@10 42 }
meillo@10 43 return res;
meillo@0 44 }
meillo@0 45
meillo@10 46 static gint
meillo@10 47 _g_list_addr_isequal(gconstpointer a, gconstpointer b)
meillo@0 48 {
meillo@10 49 address *addr1 = (address *) a;
meillo@10 50 address *addr2 = (address *) b;
meillo@10 51 int ret;
meillo@0 52
meillo@10 53 if ((ret = strcasecmp(addr1->domain, addr2->domain)) == 0)
meillo@10 54 return strcmp(addr1->local_part, addr2->local_part);
meillo@10 55 else
meillo@10 56 return ret;
meillo@0 57 }
meillo@0 58
meillo@0 59 /* accept message from anywhere.
meillo@0 60 A locally originating message is indicated by msg->recieved_host == NULL
meillo@0 61
meillo@109 62 The -t option:
meillo@109 63 The ACC_RCPT_FROM_HEAD flag adds the recipients found in To/Cc/Bcc
meillo@109 64 headers to the recipients list.
meillo@109 65 The ACC_DEL_RCPTS flag makes only sense if the ACC_RCPT_FROM_HEAD
meillo@109 66 flag is given too. With ACC_DEL_RCPTS the recipients given on the
meillo@109 67 command line are removed from the ones given in headers.
meillo@0 68 */
meillo@0 69
meillo@10 70 accept_error
meillo@10 71 accept_message_stream(FILE * in, message * msg, guint flags)
meillo@0 72 {
meillo@10 73 gchar *line, *line1;
meillo@10 74 int line_size = MAX_DATALINE;
meillo@10 75 gboolean in_headers = TRUE;
meillo@10 76 header *hdr = NULL;
meillo@10 77 gint line_cnt = 0, data_size = 0;
meillo@0 78
meillo@10 79 line = g_malloc(line_size);
meillo@14 80 line[0] = '\0';
meillo@0 81
meillo@10 82 while (TRUE) {
meillo@10 83 int len = read_sockline1(in, &line, &line_size, 5 * 60, READSOCKL_CVT_CRLF);
meillo@0 84
meillo@10 85 line1 = line;
meillo@0 86
meillo@110 87 if ((line[0] == '.') && (!(flags & ACC_DOT_IGNORE))) {
meillo@10 88 if (line[1] == '\n') {
meillo@10 89 g_free(line);
meillo@10 90 break;
meillo@10 91 }
meillo@10 92 line1++;
meillo@10 93 }
meillo@10 94
meillo@10 95 if (len <= 0) {
meillo@111 96 if ((len == -1) && (flags & (ACC_DOT_IGNORE | ACC_NODOT_RELAX))) {
meillo@10 97 /* we got an EOF, and the last line was not terminated by a CR */
meillo@10 98 gint len1 = strlen(line1);
meillo@10 99 if (len1 > 0) { /* == 0 is 'normal' (EOF after a CR) */
meillo@10 100 if (line1[len1 - 1] != '\n') { /* some mail clients allow unterminated lines */
meillo@10 101 line1[len1] = '\n';
meillo@14 102 line1[len1 + 1] = '\0';
meillo@10 103 msg->data_list = g_list_prepend(msg->data_list, g_strdup(line1));
meillo@10 104 data_size += strlen(line1);
meillo@10 105 line_cnt++;
meillo@10 106 }
meillo@10 107 }
meillo@10 108 break;
meillo@10 109 } else {
meillo@10 110 g_free(line);
meillo@10 111 if (len == -1) {
meillo@10 112 return AERR_EOF;
meillo@10 113 } else if (len == -2) {
meillo@10 114 /* should not happen any more */
meillo@10 115 return AERR_OVERFLOW;
meillo@10 116 } else if (len == -3) {
meillo@10 117 return AERR_TIMEOUT;
meillo@10 118 } else {
meillo@10 119 /* does not happen */
meillo@10 120 DEBUG(5) debugf("read_sockline returned %d\n", len);
meillo@10 121 return AERR_UNKNOWN;
meillo@10 122 }
meillo@10 123 }
meillo@10 124 } else {
meillo@10 125 if (in_headers) {
meillo@10 126
meillo@10 127 /* some pop servers send the 'From ' line, skip it: */
meillo@10 128 if (msg->hdr_list == NULL)
meillo@10 129 if (strncmp(line1, "From ", 5) == 0)
meillo@10 130 continue;
meillo@10 131
meillo@10 132 if (line1[0] == ' ' || line1[0] == '\t') {
meillo@10 133 /* continuation of 'folded' header: */
meillo@10 134 if (hdr) {
meillo@179 135 char* cp;
meillo@179 136 cp = g_strconcat(hdr->header, line1, NULL);
meillo@179 137 hdr->value = cp + (hdr->value - hdr->header);
meillo@179 138 free(hdr->header);
meillo@179 139 hdr->header = cp;
meillo@10 140 }
meillo@10 141
meillo@10 142 } else if (line1[0] == '\n') {
meillo@10 143 /* an empty line marks end of headers */
meillo@10 144 in_headers = FALSE;
meillo@10 145 } else {
meillo@10 146 /* in all other cases we expect another header */
meillo@10 147 if ((hdr = get_header(line1)))
meillo@10 148 msg->hdr_list = g_list_append(msg->hdr_list, hdr);
meillo@10 149 else {
meillo@10 150 /* if get_header() returns NULL, no header was recognized,
meillo@10 151 so this seems to be the first data line of a broken mailer
meillo@10 152 which does not send an empty line after the headers */
meillo@10 153 in_headers = FALSE;
meillo@10 154 msg->data_list = g_list_prepend(msg->data_list, g_strdup(line1));
meillo@10 155 }
meillo@10 156 }
meillo@10 157 } else {
meillo@10 158 msg->data_list = g_list_prepend(msg->data_list, g_strdup(line1));
meillo@10 159 data_size += strlen(line1);
meillo@10 160 line_cnt++;
meillo@10 161 }
meillo@10 162 }
meillo@120 163 if (conf.max_msg_size && (data_size > conf.max_msg_size)) {
meillo@117 164 DEBUG(4) debugf("accept_message_stream(): "
meillo@117 165 "received %d bytes (conf.max_msg_size=%d)\n",
meillo@117 166 data_size, conf.max_msg_size);
meillo@117 167 return AERR_SIZE;
meillo@117 168 }
meillo@117 169
meillo@0 170 }
meillo@0 171
meillo@10 172 if (msg->data_list != NULL)
meillo@10 173 msg->data_list = g_list_reverse(msg->data_list);
meillo@10 174 else
meillo@10 175 /* make sure data list is not NULL: */
meillo@10 176 msg->data_list = g_list_append(NULL, g_strdup(""));
meillo@0 177
meillo@10 178 DEBUG(4) debugf("received %d lines of data (%d bytes)\n", line_cnt, data_size);
meillo@10 179 /* we get here after we succesfully received the mail data */
meillo@0 180
meillo@10 181 msg->data_size = data_size;
meillo@10 182 msg->received_time = time(NULL);
meillo@0 183
meillo@10 184 return AERR_OK;
meillo@0 185 }
meillo@0 186
meillo@10 187 accept_error
meillo@10 188 accept_message_prepare(message * msg, guint flags)
meillo@0 189 {
meillo@10 190 struct passwd *passwd = NULL;
meillo@10 191 GList *non_rcpt_list = NULL;
meillo@10 192 time_t rec_time = time(NULL);
meillo@0 193
meillo@10 194 DEBUG(5) debugf("accept_message_prepare()\n");
meillo@0 195
meillo@10 196 /* create unique message id */
meillo@10 197 msg->uid = g_malloc(14);
meillo@0 198
meillo@10 199 string_base62(msg->uid, rec_time, 6);
meillo@10 200 msg->uid[6] = '-';
meillo@10 201 string_base62(&(msg->uid[7]), getpid(), 3);
meillo@10 202 msg->uid[10] = '-';
meillo@10 203 string_base62(&(msg->uid[11]), msg->transfer_id, 2);
meillo@10 204 msg->uid[13] = 0;
meillo@0 205
meillo@10 206 /* if local, get password entry */
meillo@10 207 if (msg->received_host == NULL) {
meillo@10 208 passwd = g_memdup(getpwuid(geteuid()), sizeof(struct passwd));
meillo@10 209 msg->ident = g_strdup(passwd->pw_name);
meillo@10 210 }
meillo@0 211
meillo@10 212 /* set return path if local */
meillo@22 213 if (msg->return_path == NULL && msg->received_host == NULL) {
meillo@22 214 gchar *path = g_strdup_printf("<%s@%s>", passwd->pw_name, conf.host_name);
meillo@22 215 DEBUG(3) debugf("setting return_path for local accept: %s\n", path);
meillo@22 216 msg->return_path = create_address(path, TRUE);
meillo@22 217 g_free(path);
meillo@10 218 }
meillo@0 219
meillo@109 220 /* -t option (see comment above) */
meillo@10 221 if (flags & ACC_DEL_RCPTS) {
meillo@10 222 non_rcpt_list = msg->rcpt_list;
meillo@10 223 msg->rcpt_list = NULL;
meillo@10 224 }
meillo@0 225
meillo@10 226 /* scan headers */
meillo@10 227 {
meillo@10 228 gboolean has_id = FALSE;
meillo@10 229 gboolean has_date = FALSE;
meillo@10 230 gboolean has_sender = FALSE;
meillo@10 231 gboolean has_from = FALSE;
meillo@10 232 gboolean has_to_or_cc = FALSE;
meillo@10 233 GList *hdr_node, *hdr_node_next;
meillo@10 234 header *hdr;
meillo@0 235
meillo@10 236 for (hdr_node = g_list_first(msg->hdr_list);
meillo@10 237 hdr_node != NULL; hdr_node = hdr_node_next) {
meillo@10 238 hdr_node_next = g_list_next(hdr_node);
meillo@10 239 hdr = ((header *) (hdr_node->data));
meillo@10 240 DEBUG(5) debugf("scanning headers: %s", hdr->header);
meillo@10 241 switch (hdr->id) {
meillo@10 242 case HEAD_MESSAGE_ID:
meillo@10 243 has_id = TRUE;
meillo@10 244 break;
meillo@10 245 case HEAD_DATE:
meillo@10 246 has_date = TRUE;
meillo@10 247 break;
meillo@10 248 case HEAD_FROM:
meillo@10 249 has_from = TRUE;
meillo@10 250 break;
meillo@10 251 case HEAD_SENDER:
meillo@10 252 has_sender = TRUE;
meillo@10 253 break;
meillo@10 254 case HEAD_TO:
meillo@10 255 case HEAD_CC:
meillo@106 256 has_to_or_cc = TRUE;
meillo@106 257 /* fall through */
meillo@10 258 case HEAD_BCC:
meillo@10 259 if (flags & ACC_RCPT_FROM_HEAD) {
meillo@109 260 /* -t option (see comment above) */
meillo@10 261 DEBUG(5) debugf("hdr->value = %s\n", hdr->value);
meillo@10 262 if (hdr->value) {
meillo@10 263 msg->rcpt_list = addr_list_append_rfc822(msg->rcpt_list, hdr->value, conf.host_name);
meillo@10 264 }
meillo@10 265 }
meillo@106 266 if (hdr->id == HEAD_BCC) {
meillo@10 267 DEBUG(3) debugf("removing 'Bcc' header\n");
meillo@10 268 msg->hdr_list = g_list_remove_link(msg->hdr_list, hdr_node);
meillo@10 269 g_list_free_1(hdr_node);
meillo@10 270 destroy_header(hdr);
meillo@106 271 }
meillo@10 272 break;
meillo@10 273 case HEAD_ENVELOPE_TO:
meillo@10 274 if (flags & ACC_SAVE_ENVELOPE_TO) {
meillo@10 275 DEBUG(3) debugf("creating 'X-Orig-Envelope-To' header\n");
meillo@22 276 msg->hdr_list = g_list_prepend(msg->hdr_list, create_header(HEAD_UNKNOWN,
meillo@22 277 "X-Orig-Envelope-to: %s", hdr->value));
meillo@10 278 }
meillo@10 279 DEBUG(3) debugf("removing 'Envelope-To' header\n");
meillo@10 280 msg->hdr_list = g_list_remove_link(msg->hdr_list, hdr_node);
meillo@10 281 g_list_free_1(hdr_node);
meillo@10 282 destroy_header(hdr);
meillo@10 283 break;
meillo@10 284 case HEAD_RETURN_PATH:
meillo@10 285 if (flags & ACC_MAIL_FROM_HEAD) {
meillo@10 286 /* usually POP3 accept */
meillo@10 287 msg->return_path = create_address_qualified(hdr->value, TRUE, msg->received_host);
meillo@10 288 DEBUG(3) debugf("setting return_path to %s\n", addr_string(msg->return_path));
meillo@10 289 }
meillo@10 290 DEBUG(3) debugf("removing 'Return-Path' header\n");
meillo@10 291 msg->hdr_list = g_list_remove_link(msg->hdr_list, hdr_node);
meillo@10 292 g_list_free_1(hdr_node);
meillo@10 293 destroy_header(hdr);
meillo@10 294 break;
meillo@10 295 default:
meillo@10 296 break; /* make compiler happy */
meillo@10 297 }
meillo@10 298 }
meillo@10 299
meillo@10 300 if (msg->return_path == NULL) {
meillo@10 301 /* this can happen for pop3 accept only and if no Return-path: header was given */
meillo@10 302 GList *hdr_list;
meillo@10 303 header *hdr;
meillo@10 304
meillo@10 305 DEBUG(3) debugf("return_path == NULL\n");
meillo@10 306
meillo@10 307 hdr_list = find_header(msg->hdr_list, HEAD_SENDER, NULL);
meillo@10 308 if (!hdr_list)
meillo@10 309 hdr_list = find_header(msg->hdr_list, HEAD_FROM, NULL);
meillo@10 310 if (hdr_list) {
meillo@10 311 gchar *addr;
meillo@10 312 hdr = (header *) (g_list_first(hdr_list)->data);
meillo@10 313
meillo@10 314 DEBUG(5) debugf("hdr->value = '%s'\n", hdr->value);
meillo@10 315
meillo@10 316 addr = g_strdup(hdr->value);
meillo@10 317 g_strchomp(addr);
meillo@10 318
meillo@10 319 if ((msg->return_path = create_address_qualified(addr, FALSE, msg->received_host)) != NULL) {
meillo@10 320 DEBUG(3) debugf("setting return_path to %s\n", addr_string(msg->return_path));
meillo@13 321 msg->hdr_list = g_list_append(msg->hdr_list, create_header(HEAD_UNKNOWN,
meillo@13 322 "X-Warning: return path set from %s address\n",
meillo@13 323 hdr->id == HEAD_SENDER ? "Sender:" : "From:"));
meillo@10 324 }
meillo@10 325 g_free(addr);
meillo@10 326 }
meillo@10 327 if (msg->return_path == NULL) { /* no Sender: or From: or create_address_qualified failed */
meillo@10 328 msg->return_path = create_address_qualified("postmaster", TRUE, conf.host_name);
meillo@10 329 DEBUG(3) debugf("setting return_path to %s\n", addr_string(msg->return_path));
meillo@13 330 msg->hdr_list = g_list_append(msg->hdr_list, create_header(HEAD_UNKNOWN,
meillo@25 331 "X-Warning: real return path is unknown\n"));
meillo@10 332 }
meillo@10 333 }
meillo@10 334
meillo@10 335 if (flags & ACC_DEL_RCPTS) {
meillo@109 336 /* remove the recipients given on the command line from the ones given in headers
meillo@109 337 -t option (see comment above) */
meillo@10 338 GList *rcpt_node;
meillo@10 339 foreach(non_rcpt_list, rcpt_node) {
meillo@10 340 address *rcpt = (address *) (rcpt_node->data);
meillo@10 341 GList *node;
meillo@10 342 if ((node = g_list_find_custom(msg->rcpt_list, rcpt, _g_list_addr_isequal))) {
meillo@10 343 DEBUG(3) debugf("removing rcpt address %s\n", addr_string(node->data));
meillo@10 344 msg->rcpt_list = g_list_remove_link(msg->rcpt_list, node);
meillo@10 345 destroy_address((address *) (node->data));
meillo@10 346 g_list_free_1(node);
meillo@10 347 }
meillo@10 348 }
meillo@10 349 }
meillo@10 350
meillo@10 351 /* here we should have our recipients, fail if not: */
meillo@10 352 if (msg->rcpt_list == NULL) {
meillo@10 353 logwrite(LOG_WARNING, "no recipients found in message\n");
meillo@10 354 return AERR_NORCPT;
meillo@10 355 }
meillo@10 356
meillo@10 357 if (!(has_sender || has_from)) {
meillo@10 358 DEBUG(3) debugf("adding 'From' header\n");
meillo@10 359 msg->hdr_list = g_list_append(msg->hdr_list,
meillo@10 360 msg->full_sender_name
meillo@10 361 ?
meillo@13 362 create_header(HEAD_FROM, "From: \"%s\" <%s@%s>\n", msg->full_sender_name,
meillo@13 363 msg->return_path->local_part, msg->return_path->domain)
meillo@10 364 :
meillo@10 365 create_header(HEAD_FROM, "From: <%s@%s>\n",
meillo@13 366 msg->return_path->local_part, msg->return_path->domain)
meillo@10 367 );
meillo@10 368 }
meillo@106 369 if (!has_to_or_cc) {
meillo@106 370 DEBUG(3) debugf("no To: or Cc: header, hence adding `To: undisclosed recipients:;'\n");
meillo@105 371 msg->hdr_list = g_list_append(msg->hdr_list, create_header(HEAD_TO, "To: undisclosed-recipients:;\n"));
meillo@10 372 }
meillo@10 373 if (!has_date) {
meillo@10 374 DEBUG(3) debugf("adding 'Date:' header\n");
meillo@10 375 msg->hdr_list = g_list_append(msg->hdr_list, create_header(HEAD_DATE, "Date: %s\n", rec_timestamp()));
meillo@10 376 }
meillo@10 377 if (!has_id) {
meillo@10 378 DEBUG(3) debugf("adding 'Message-ID:' header\n");
meillo@13 379 msg->hdr_list = g_list_append(msg->hdr_list,
meillo@13 380 create_header(HEAD_MESSAGE_ID, "Message-ID: <%s@%s>\n", msg->uid, conf.host_name));
meillo@10 381 }
meillo@0 382 }
meillo@10 383
meillo@10 384 /* Received header: */
meillo@10 385 /* At this point because we have to know the rcpts for the 'for' part */
meillo@102 386 gchar *for_string = NULL;
meillo@102 387 header *hdr = NULL;
meillo@10 388
meillo@102 389 DEBUG(3) debugf("adding 'Received:' header\n");
meillo@10 390
meillo@102 391 if (g_list_length(msg->rcpt_list) == 1) {
meillo@102 392 address *addr = (address *) (g_list_first(msg->rcpt_list)->data);
meillo@102 393 for_string = g_strdup_printf(" for %s", addr_string(addr));
meillo@102 394 }
meillo@10 395
meillo@102 396 if (msg->received_host == NULL) {
meillo@102 397 /* received locally */
meillo@102 398 hdr = create_header(HEAD_RECEIVED, "Received: from %s by %s with %s (%s %s) id %s%s; %s\n",
meillo@102 399 passwd->pw_name, conf.host_name, prot_names[msg->received_prot],
meillo@102 400 PACKAGE, VERSION, msg->uid, for_string ? for_string : "", rec_timestamp());
meillo@102 401 } else {
meillo@102 402 /* received from remote */
meillo@10 403 #ifdef ENABLE_IDENT
meillo@102 404 DEBUG(5) debugf("adding 'Received:' header (5)\n");
meillo@102 405 hdr = create_header(HEAD_RECEIVED, "Received: from %s (ident=%s) by %s with %s (%s %s) id %s%s; %s\n",
meillo@102 406 msg->received_host, msg->ident ? msg->ident : "unknown", conf.host_name,
meillo@102 407 prot_names[msg->received_prot], PACKAGE, VERSION, msg->uid, for_string ? for_string : "",
meillo@102 408 rec_timestamp());
meillo@10 409 #else
meillo@102 410 hdr = create_header(HEAD_RECEIVED, "Received: from %s by %s with %s (%s %s) id %s%s; %s\n",
meillo@102 411 msg->received_host, conf.host_name, prot_names[msg->received_prot],
meillo@102 412 PACKAGE, VERSION, msg->uid, for_string ? for_string : "", rec_timestamp());
meillo@10 413 #endif
meillo@102 414 }
meillo@102 415 header_fold(hdr);
meillo@102 416 msg->hdr_list = g_list_prepend(msg->hdr_list, hdr);
meillo@10 417
meillo@102 418 if (for_string)
meillo@102 419 g_free(for_string);
meillo@0 420
meillo@10 421 /* write message to spool: */
meillo@10 422 /* accept is no longer responsible for this
meillo@13 423 if (!spool_write(msg, TRUE))
meillo@13 424 return AERR_NOSPOOL;
meillo@10 425 */
meillo@10 426 return AERR_OK;
meillo@0 427 }
meillo@0 428
meillo@10 429 accept_error
meillo@10 430 accept_message(FILE * in, message * msg, guint flags)
meillo@0 431 {
meillo@10 432 accept_error err;
meillo@0 433
meillo@10 434 err = accept_message_stream(in, msg, flags);
meillo@10 435 if (err == AERR_OK)
meillo@10 436 err = accept_message_prepare(msg, flags);
meillo@0 437
meillo@10 438 return err;
meillo@0 439 }