masqmail

annotate src/route.c @ 222:8cddc65765bd

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 Fri, 23 Jul 2010 10:57:53 +0200
parents f671821d8222
children 5f9f3a65032e
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 <fnmatch.h>
meillo@15 20
meillo@0 21 #include "masqmail.h"
meillo@0 22
meillo@10 23 msgout_perhost*
meillo@10 24 create_msgout_perhost(gchar * host)
meillo@0 25 {
meillo@10 26 msgout_perhost *mo_ph = g_malloc(sizeof(msgout_perhost));
meillo@10 27 if (mo_ph) {
meillo@10 28 mo_ph->host = g_strdup(host);
meillo@10 29 mo_ph->msgout_list = NULL;
meillo@10 30 }
meillo@10 31 return mo_ph;
meillo@0 32 }
meillo@0 33
meillo@10 34 void
meillo@10 35 destroy_msgout_perhost(msgout_perhost * mo_ph)
meillo@0 36 {
meillo@10 37 GList *mo_node;
meillo@0 38
meillo@10 39 foreach(mo_ph->msgout_list, mo_node) {
meillo@10 40 msg_out *mo = (msg_out *) (mo_node->data);
meillo@10 41 /* the rcpt_list is owned by the msgout's, but not the rcpt's themselves */
meillo@10 42 g_list_free(mo->rcpt_list);
meillo@10 43 g_free(mo);
meillo@10 44 }
meillo@10 45 g_list_free(mo_ph->msgout_list);
meillo@10 46 g_free(mo_ph);
meillo@0 47 }
meillo@0 48
meillo@10 49 void
meillo@10 50 rewrite_headers(msg_out * msgout, connect_route * route)
meillo@0 51 {
meillo@10 52 /* if set_h_from_domain is set, replace domain in all
meillo@10 53 From: headers.
meillo@10 54 */
meillo@10 55 msgout->hdr_list = g_list_copy(msgout->msg->hdr_list);
meillo@0 56
meillo@10 57 /* map from addresses */
meillo@10 58 if (route->map_h_from_addresses != NULL) {
meillo@10 59 GList *hdr_node;
meillo@10 60 foreach(msgout->hdr_list, hdr_node) {
meillo@10 61 header *hdr = (header *) (hdr_node->data);
meillo@10 62 if (hdr->id == HEAD_FROM) {
meillo@10 63 header *new_hdr = copy_header(hdr);
meillo@10 64 if (map_address_header(new_hdr, route->map_h_from_addresses)) {
meillo@10 65 hdr_node->data = new_hdr;
meillo@10 66 /* we need this list only to carefully free the extra headers: */
meillo@10 67 msgout->xtra_hdr_list = g_list_append(msgout->xtra_hdr_list, new_hdr);
meillo@10 68 } else
meillo@10 69 g_free(new_hdr);
meillo@10 70 }
meillo@10 71 }
meillo@10 72 } else {
meillo@10 73 /* replace from domain */
meillo@10 74 if (route->set_h_from_domain != NULL) {
meillo@10 75 GList *hdr_node;
meillo@10 76
meillo@10 77 foreach(msgout->hdr_list, hdr_node) {
meillo@10 78 header *hdr = (header *) (hdr_node->data);
meillo@10 79 if (hdr->id == HEAD_FROM) {
meillo@10 80 header *new_hdr = copy_header(hdr);
meillo@10 81
meillo@10 82 DEBUG(5) debugf("setting From: domain to %s\n", route->set_h_from_domain);
meillo@10 83 if (set_address_header_domain(new_hdr, route->set_h_from_domain)) {
meillo@10 84 hdr_node->data = new_hdr;
meillo@10 85 /* we need this list only to carefully free the extra headers: */
meillo@10 86 DEBUG(6) debugf("header = %s\n", new_hdr->header);
meillo@10 87 msgout->xtra_hdr_list = g_list_append(msgout->xtra_hdr_list, new_hdr);
meillo@10 88 } else {
meillo@15 89 logwrite(LOG_ALERT, "error in set_address_header_domain(%s, %s)\n",
meillo@15 90 new_hdr->value, route->set_h_from_domain);
meillo@10 91 }
meillo@10 92 }
meillo@10 93 }
meillo@10 94 }
meillo@0 95 }
meillo@0 96
meillo@10 97 /* map reply-to addresses */
meillo@10 98 if (route->map_h_reply_to_addresses != NULL) {
meillo@10 99 GList *hdr_node;
meillo@10 100 foreach(msgout->hdr_list, hdr_node) {
meillo@10 101 header *hdr = (header *) (hdr_node->data);
meillo@10 102 if (hdr->id == HEAD_REPLY_TO) {
meillo@10 103 header *new_hdr = copy_header(hdr);
meillo@10 104 if (map_address_header
meillo@10 105 (new_hdr, route->map_h_reply_to_addresses)) {
meillo@10 106 hdr_node->data = new_hdr;
meillo@10 107 /* we need this list only to carefully free the extra headers: */
meillo@10 108 msgout->xtra_hdr_list = g_list_append(msgout->xtra_hdr_list, new_hdr);
meillo@10 109 } else
meillo@10 110 g_free(new_hdr);
meillo@10 111 }
meillo@10 112 }
meillo@10 113 } else {
meillo@10 114 /* replace Reply-to domain */
meillo@10 115 if (route->set_h_reply_to_domain != NULL) {
meillo@10 116 GList *hdr_node;
meillo@10 117
meillo@10 118 foreach(msgout->hdr_list, hdr_node) {
meillo@10 119 header *hdr = (header *) (hdr_node->data);
meillo@10 120 if (hdr->id == HEAD_REPLY_TO) {
meillo@10 121 header *new_hdr = copy_header(hdr);
meillo@10 122
meillo@10 123 set_address_header_domain(new_hdr, route-> set_h_reply_to_domain);
meillo@10 124 hdr_node->data = new_hdr;
meillo@10 125 /* we need this list only to carefully free the extra headers: */
meillo@10 126 msgout->xtra_hdr_list = g_list_append(msgout->xtra_hdr_list, new_hdr);
meillo@10 127 }
meillo@10 128 }
meillo@10 129 }
meillo@0 130 }
meillo@0 131
meillo@10 132 /* map Mail-Followup-To addresses */
meillo@10 133 if (route->map_h_mail_followup_to_addresses != NULL) {
meillo@10 134 GList *hdr_node;
meillo@10 135 foreach(msgout->hdr_list, hdr_node) {
meillo@10 136 header *hdr = (header *) (hdr_node->data);
meillo@10 137 if (strncasecmp(hdr->header, "Mail-Followup-To", 16) == 0) {
meillo@10 138 header *new_hdr = copy_header(hdr);
meillo@10 139 if (map_address_header(new_hdr, route->map_h_mail_followup_to_addresses)) {
meillo@10 140 hdr_node->data = new_hdr;
meillo@10 141 /* we need this list only to carefully free the extra headers: */
meillo@10 142 msgout->xtra_hdr_list = g_list_append(msgout->xtra_hdr_list, new_hdr);
meillo@10 143 } else
meillo@10 144 g_free(new_hdr);
meillo@10 145 }
meillo@10 146 }
meillo@10 147 }
meillo@0 148
meillo@10 149 /* set Sender: domain to return_path->domain */
meillo@10 150 if (route->expand_h_sender_domain) {
meillo@10 151 GList *hdr_node;
meillo@0 152
meillo@10 153 foreach(msgout->hdr_list, hdr_node) {
meillo@10 154 header *hdr = (header *) (hdr_node->data);
meillo@10 155 if (hdr->id == HEAD_SENDER) {
meillo@10 156 header *new_hdr = copy_header(hdr);
meillo@0 157
meillo@10 158 set_address_header_domain(new_hdr, msgout->return_path->domain);
meillo@10 159 hdr_node->data = new_hdr;
meillo@10 160 /* we need this list only to carefully free the extra headers: */
meillo@10 161 msgout->xtra_hdr_list = g_list_append(msgout->xtra_hdr_list, new_hdr);
meillo@10 162 }
meillo@10 163 }
meillo@10 164 }
meillo@0 165
meillo@10 166 /* set Sender: domain to return_path->domain */
meillo@10 167 if (route->expand_h_sender_address) {
meillo@10 168 GList *hdr_node;
meillo@0 169
meillo@10 170 foreach(msgout->hdr_list, hdr_node) {
meillo@10 171 header *hdr = (header *) (hdr_node->data);
meillo@10 172 if (hdr->id == HEAD_SENDER) {
meillo@10 173 header *new_hdr;
meillo@0 174
meillo@15 175 new_hdr = create_header(HEAD_SENDER, "Sender: %s@%s\n",
meillo@15 176 msgout->return_path->local_part, msgout->return_path->domain);
meillo@10 177 hdr_node->data = new_hdr;
meillo@10 178 /* we need this list only to carefully free the extra headers: */
meillo@10 179 msgout->xtra_hdr_list = g_list_append(msgout->xtra_hdr_list, new_hdr);
meillo@10 180 }
meillo@10 181 }
meillo@10 182 }
meillo@0 183
meillo@10 184 if (msgout->xtra_hdr_list == NULL) {
meillo@10 185 /* nothing was changed */
meillo@10 186 g_list_free(msgout->hdr_list);
meillo@10 187 msgout->hdr_list = NULL;
meillo@10 188 }
meillo@10 189 DEBUG(5) debugf("rewrite_headers() returning\n");
meillo@0 190 }
meillo@0 191
meillo@10 192 void
meillo@10 193 rcptlist_with_one_of_hostlist(GList * rcpt_list, GList * host_list, GList ** p_rcpt_list, GList ** p_non_rcpt_list)
meillo@0 194 {
meillo@10 195 GList *rcpt_node;
meillo@0 196
meillo@10 197 if (rcpt_list == NULL)
meillo@10 198 return;
meillo@0 199
meillo@10 200 foreach(rcpt_list, rcpt_node) {
meillo@10 201 address *rcpt = (address *) (rcpt_node->data);
meillo@10 202 GList *host_node = NULL;
meillo@0 203
meillo@10 204 foreach(host_list, host_node) {
meillo@10 205 gchar *host = (gchar *) (host_node->data);
meillo@10 206 if (fnmatch(host, rcpt->domain, FNM_CASEFOLD) == 0)
meillo@10 207 break;
meillo@10 208 }
meillo@10 209 if (host_node) {
meillo@10 210 if (p_rcpt_list)
meillo@10 211 *p_rcpt_list = g_list_append(*p_rcpt_list, rcpt);
meillo@10 212 } else {
meillo@10 213 if (p_non_rcpt_list)
meillo@10 214 *p_non_rcpt_list = g_list_append(*p_non_rcpt_list, rcpt);
meillo@10 215 }
meillo@0 216
meillo@10 217 }
meillo@0 218 }
meillo@0 219
meillo@10 220 void
meillo@10 221 rcptlist_with_addr_is_local(GList * rcpt_list, GList ** p_rcpt_list, GList ** p_non_rcpt_list)
meillo@0 222 {
meillo@10 223 GList *rcpt_node;
meillo@0 224
meillo@10 225 if (rcpt_list == NULL)
meillo@10 226 return;
meillo@0 227
meillo@10 228 foreach(rcpt_list, rcpt_node) {
meillo@10 229 address *rcpt = (address *) (rcpt_node->data);
meillo@10 230 if (addr_is_local(rcpt)) {
meillo@10 231 if (p_rcpt_list)
meillo@10 232 *p_rcpt_list = g_list_append(*p_rcpt_list, rcpt);
meillo@10 233 } else {
meillo@10 234 if (p_non_rcpt_list)
meillo@10 235 *p_non_rcpt_list = g_list_append(*p_non_rcpt_list, rcpt);
meillo@10 236 }
meillo@0 237
meillo@10 238 }
meillo@0 239 }
meillo@0 240
meillo@10 241 static gint
meillo@10 242 _g_list_addrcmp(gconstpointer a, gconstpointer b)
meillo@0 243 {
meillo@10 244 return addr_match((address *) a, (address *) b);
meillo@0 245 }
meillo@0 246
meillo@10 247 gboolean
meillo@10 248 route_is_allowed_return_path(connect_route * route, address * ret_path)
meillo@0 249 {
meillo@10 250 if (route->not_allowed_return_paths != NULL) {
meillo@10 251 if (g_list_find_custom(route->not_allowed_return_paths, ret_path, _g_list_addrcmp) != NULL) {
meillo@10 252 return FALSE;
meillo@10 253 }
meillo@10 254 }
meillo@10 255 if (route->allowed_return_paths != NULL) {
meillo@10 256 if (g_list_find_custom(route->allowed_return_paths, ret_path, _g_list_addrcmp) != NULL) {
meillo@10 257 return TRUE;
meillo@10 258 } else {
meillo@10 259 return FALSE;
meillo@10 260 }
meillo@10 261 }
meillo@10 262 return TRUE;
meillo@0 263 }
meillo@0 264
meillo@10 265 static gint
meillo@10 266 _g_list_strcmp(gconstpointer a, gconstpointer b)
meillo@0 267 {
meillo@10 268 return (gint) strcmp(a, b);
meillo@0 269 }
meillo@0 270
meillo@10 271 gboolean
meillo@10 272 route_is_allowed_mail_local(connect_route * route, address * ret_path)
meillo@0 273 {
meillo@10 274 gchar *loc_part = ret_path->local_part;
meillo@0 275
meillo@10 276 if (route->not_allowed_mail_locals != NULL) {
meillo@10 277 if (g_list_find_custom(route->not_allowed_mail_locals, loc_part, _g_list_strcmp) != NULL)
meillo@10 278 return FALSE;
meillo@10 279 }
meillo@10 280 if (route->allowed_mail_locals != NULL) {
meillo@10 281 if (g_list_find_custom(route->allowed_mail_locals, loc_part, _g_list_strcmp) != NULL)
meillo@10 282 return TRUE;
meillo@10 283 else
meillo@10 284 return FALSE;
meillo@10 285 }
meillo@10 286 return TRUE;
meillo@0 287 }
meillo@0 288
meillo@10 289 /*
meillo@0 290 Make lists of matching/not matching rcpts.
meillo@0 291 Local domains are NOT regared here, these should be sorted out previously
meillo@0 292 */
meillo@10 293 void
meillo@10 294 msg_rcptlist_route(connect_route * route, GList * rcpt_list, GList ** p_rcpt_list, GList ** p_non_rcpt_list)
meillo@0 295 {
meillo@10 296 GList *tmp_list = NULL;
meillo@10 297 /* sort out those domains that can be sent over this connection: */
meillo@10 298 if (route->allowed_rcpt_domains) {
meillo@10 299 DEBUG(5) debugf("testing for route->allowed_rcpt_domains\n");
meillo@10 300 rcptlist_with_one_of_hostlist(rcpt_list, route->allowed_rcpt_domains, &tmp_list, p_non_rcpt_list);
meillo@10 301 } else {
meillo@10 302 DEBUG(5) debugf("route->allowed_rcpt_domains == NULL\n");
meillo@10 303 tmp_list = g_list_copy(rcpt_list);
meillo@10 304 }
meillo@0 305
meillo@10 306 /* sort out those domains that cannot be sent over this connection: */
meillo@10 307 rcptlist_with_one_of_hostlist(tmp_list, route->not_allowed_rcpt_domains, p_non_rcpt_list, p_rcpt_list);
meillo@10 308 g_list_free(tmp_list);
meillo@0 309 }
meillo@0 310
meillo@10 311 msg_out*
meillo@10 312 route_prepare_msgout(connect_route * route, msg_out * msgout)
meillo@0 313 {
meillo@10 314 message *msg = msgout->msg;
meillo@10 315 GList *rcpt_list = msgout->rcpt_list;
meillo@0 316
meillo@10 317 if (rcpt_list != NULL) {
meillo@10 318 /* found a few */
meillo@10 319 DEBUG(5) {
meillo@10 320 GList *node;
meillo@10 321 debugf("rcpts for routed delivery, route = %s, id = %s\n", route->name, msg->uid);
meillo@10 322 foreach(rcpt_list, node) {
meillo@10 323 address *rcpt = (address *) (node->data);
meillo@114 324 debugf(" rcpt for routed delivery: <%s@%s>\n",
meillo@114 325 rcpt->local_part, rcpt->domain);
meillo@10 326 }
meillo@10 327 }
meillo@0 328
meillo@15 329 /* rewrite return path if there is a table, use that
meillo@10 330 if an address is found and if it has a domain, use that
meillo@10 331 */
meillo@10 332 if (route->map_return_path_addresses) {
meillo@10 333 address *ret_path = NULL;
meillo@10 334 DEBUG(5) debugf("looking up %s in map_return_path_addresses\n", msg->return_path->local_part);
meillo@10 335 ret_path = (address *) table_find_fnmatch(route->map_return_path_addresses, msg->return_path->local_part);
meillo@10 336 if (ret_path) {
meillo@10 337 DEBUG(5) debugf("found <%s@%s>\n", ret_path->local_part, ret_path->domain);
meillo@10 338 if (ret_path->domain == NULL)
meillo@10 339 ret_path->domain = route->set_return_path_domain
meillo@10 340 ? route->set_return_path_domain
meillo@10 341 : msg->return_path->domain;
meillo@10 342 msgout->return_path = copy_address(ret_path);
meillo@10 343 }
meillo@10 344 }
meillo@10 345 if (msgout->return_path == NULL) {
meillo@10 346 DEBUG(5) debugf("setting return path to %s\n", route->set_return_path_domain);
meillo@10 347 msgout->return_path = copy_modify_address(msg->return_path, NULL, route->set_return_path_domain);
meillo@10 348 }
meillo@10 349 rewrite_headers(msgout, route);
meillo@10 350
meillo@10 351 return msgout;
meillo@10 352 }
meillo@10 353 return NULL;
meillo@0 354 }
meillo@0 355
meillo@0 356 /* put msgout's is msgout_list into bins (msgout_perhost structs) for each
meillo@0 357 host. Used if there is no mail_host.
meillo@0 358 route param is not used, we leave it here because that may change.
meillo@0 359 */
meillo@0 360
meillo@10 361 GList*
meillo@10 362 route_msgout_list(connect_route * route, GList * msgout_list)
meillo@0 363 {
meillo@10 364 GList *mo_ph_list = NULL;
meillo@10 365 GList *msgout_node;
meillo@0 366
meillo@10 367 foreach(msgout_list, msgout_node) {
meillo@10 368 msg_out *msgout = (msg_out *) (msgout_node->data);
meillo@10 369 msg_out *msgout_new;
meillo@10 370 GList *rcpt_list = msgout->rcpt_list;
meillo@10 371 GList *rcpt_node;
meillo@0 372
meillo@10 373 foreach(rcpt_list, rcpt_node) {
meillo@10 374 address *rcpt = rcpt_node->data;
meillo@10 375 msgout_perhost *mo_ph = NULL;
meillo@10 376 GList *mo_ph_node = NULL;
meillo@0 377
meillo@10 378 /* search host in mo_ph_list */
meillo@10 379 foreach(mo_ph_list, mo_ph_node) {
meillo@10 380 mo_ph = (msgout_perhost *) (mo_ph_node->data);
meillo@10 381 if (strcasecmp(mo_ph->host, rcpt->domain) == 0)
meillo@10 382 break;
meillo@10 383 }
meillo@10 384 if (mo_ph_node != NULL) {
meillo@10 385 /* there is already a rcpt for this host */
meillo@10 386 msg_out *msgout_last = (msg_out *) ((g_list_last(mo_ph->msgout_list))->data);
meillo@10 387 if (msgout_last->msg == msgout->msg) {
meillo@15 388 /* if it is also the same message, it must be the last one appended
meillo@15 389 to mo_ph->msgout_list (since outer loop goes through msgout_list) */
meillo@10 390 msgout_last->rcpt_list = g_list_append(msgout_last->rcpt_list, rcpt);
meillo@10 391 } else {
meillo@10 392 /* if not, we append a new msgout */
meillo@10 393 /* make a copy of msgout */
meillo@10 394 msgout_new = create_msg_out(msgout->msg);
meillo@10 395 msgout_new->return_path = msgout->return_path;
meillo@10 396 msgout_new->hdr_list = msgout->hdr_list;
meillo@0 397
meillo@10 398 /* append our rcpt to it */
meillo@10 399 /* It is the 1st rcpt for this msg to this host, therefore we safely give NULL */
meillo@10 400 msgout_new->rcpt_list = g_list_append(NULL, rcpt);
meillo@10 401 mo_ph->msgout_list = g_list_append(mo_ph->msgout_list, msgout_new);
meillo@10 402 }
meillo@10 403 } else {
meillo@10 404 /* this rcpt to goes to another host */
meillo@10 405 mo_ph = create_msgout_perhost(rcpt->domain);
meillo@10 406 mo_ph_list = g_list_append(mo_ph_list, mo_ph);
meillo@0 407
meillo@10 408 /* make a copy of msgout */
meillo@10 409 msgout_new = create_msg_out(msgout->msg);
meillo@10 410 msgout_new->return_path = msgout->return_path;
meillo@10 411 msgout_new->hdr_list = msgout->hdr_list;
meillo@0 412
meillo@10 413 /* append our rcpt to it */
meillo@10 414 /* It is the 1st rcpt for this msg to this host, therefore we safely give NULL */
meillo@10 415 msgout_new->rcpt_list = g_list_append(NULL, rcpt);
meillo@10 416 mo_ph->msgout_list = g_list_append(mo_ph->msgout_list, msgout_new);
meillo@10 417 } /* if mo_ph != NULL */
meillo@10 418 } /* foreach(rcpt_list, ... */
meillo@10 419 } /* foreach(msgout_list, ... */
meillo@10 420
meillo@10 421 return mo_ph_list;
meillo@0 422 }