masqmail

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