masqmail

annotate src/route.c @ 378:5781ba87df95

Removed ident. This had been discussed on the mailing list in Oct 2011. Ident is hardly useful in typical setups for masqmail. Probably Oliver had used it in his setup; that would make sense. Now, I know of nobody who needs it.
author markus schnalke <meillo@marmaro.de>
date Sat, 14 Jan 2012 21:36:58 +0100
parents b27f66555ba8
children 5593964ec779
rev   line source
meillo@367 1 /*
meillo@367 2 ** MasqMail
meillo@367 3 ** Copyright (C) 1999-2001 Oliver Kurth
meillo@367 4 ** Copyright (C) 2010 markus schnalke <meillo@marmaro.de>
meillo@367 5 **
meillo@367 6 ** This program is free software; you can redistribute it and/or modify
meillo@367 7 ** it under the terms of the GNU General Public License as published by
meillo@367 8 ** the Free Software Foundation; either version 2 of the License, or
meillo@367 9 ** (at your option) any later version.
meillo@367 10 **
meillo@367 11 ** This program is distributed in the hope that it will be useful,
meillo@367 12 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
meillo@367 13 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
meillo@367 14 ** GNU General Public License for more details.
meillo@367 15 **
meillo@367 16 ** You should have received a copy of the GNU General Public License
meillo@367 17 ** along with this program; if not, write to the Free Software
meillo@367 18 ** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
meillo@0 19 */
meillo@0 20
meillo@15 21 #include <fnmatch.h>
meillo@15 22
meillo@0 23 #include "masqmail.h"
meillo@0 24
meillo@10 25 msgout_perhost*
meillo@366 26 create_msgout_perhost(gchar *host)
meillo@0 27 {
meillo@10 28 msgout_perhost *mo_ph = g_malloc(sizeof(msgout_perhost));
meillo@10 29 if (mo_ph) {
meillo@10 30 mo_ph->host = g_strdup(host);
meillo@10 31 mo_ph->msgout_list = NULL;
meillo@10 32 }
meillo@10 33 return mo_ph;
meillo@0 34 }
meillo@0 35
meillo@10 36 void
meillo@366 37 destroy_msgout_perhost(msgout_perhost *mo_ph)
meillo@0 38 {
meillo@10 39 GList *mo_node;
meillo@0 40
meillo@10 41 foreach(mo_ph->msgout_list, mo_node) {
meillo@10 42 msg_out *mo = (msg_out *) (mo_node->data);
meillo@10 43 /* the rcpt_list is owned by the msgout's, but not the rcpt's themselves */
meillo@10 44 g_list_free(mo->rcpt_list);
meillo@10 45 g_free(mo);
meillo@10 46 }
meillo@10 47 g_list_free(mo_ph->msgout_list);
meillo@10 48 g_free(mo_ph);
meillo@0 49 }
meillo@0 50
meillo@10 51 void
meillo@366 52 rewrite_headers(msg_out *msgout, connect_route *route)
meillo@0 53 {
meillo@367 54 /*
meillo@367 55 ** if set_h_from_domain is set, replace domain in all
meillo@367 56 ** From: headers.
meillo@367 57 */
meillo@10 58 msgout->hdr_list = g_list_copy(msgout->msg->hdr_list);
meillo@0 59
meillo@10 60 /* map from addresses */
meillo@10 61 if (route->map_h_from_addresses != NULL) {
meillo@10 62 GList *hdr_node;
meillo@10 63 foreach(msgout->hdr_list, hdr_node) {
meillo@10 64 header *hdr = (header *) (hdr_node->data);
meillo@10 65 if (hdr->id == HEAD_FROM) {
meillo@10 66 header *new_hdr = copy_header(hdr);
meillo@10 67 if (map_address_header(new_hdr, route->map_h_from_addresses)) {
meillo@10 68 hdr_node->data = new_hdr;
meillo@10 69 /* we need this list only to carefully free the extra headers: */
meillo@10 70 msgout->xtra_hdr_list = g_list_append(msgout->xtra_hdr_list, new_hdr);
meillo@10 71 } else
meillo@10 72 g_free(new_hdr);
meillo@10 73 }
meillo@10 74 }
meillo@10 75 } else {
meillo@10 76 /* replace from domain */
meillo@10 77 if (route->set_h_from_domain != NULL) {
meillo@10 78 GList *hdr_node;
meillo@10 79
meillo@10 80 foreach(msgout->hdr_list, hdr_node) {
meillo@10 81 header *hdr = (header *) (hdr_node->data);
meillo@10 82 if (hdr->id == HEAD_FROM) {
meillo@10 83 header *new_hdr = copy_header(hdr);
meillo@10 84
meillo@10 85 DEBUG(5) debugf("setting From: domain to %s\n", route->set_h_from_domain);
meillo@10 86 if (set_address_header_domain(new_hdr, route->set_h_from_domain)) {
meillo@10 87 hdr_node->data = new_hdr;
meillo@10 88 /* we need this list only to carefully free the extra headers: */
meillo@10 89 DEBUG(6) debugf("header = %s\n", new_hdr->header);
meillo@10 90 msgout->xtra_hdr_list = g_list_append(msgout->xtra_hdr_list, new_hdr);
meillo@10 91 } else {
meillo@15 92 logwrite(LOG_ALERT, "error in set_address_header_domain(%s, %s)\n",
meillo@15 93 new_hdr->value, route->set_h_from_domain);
meillo@10 94 }
meillo@10 95 }
meillo@10 96 }
meillo@10 97 }
meillo@0 98 }
meillo@0 99
meillo@10 100 /* map reply-to addresses */
meillo@10 101 if (route->map_h_reply_to_addresses != NULL) {
meillo@10 102 GList *hdr_node;
meillo@10 103 foreach(msgout->hdr_list, hdr_node) {
meillo@10 104 header *hdr = (header *) (hdr_node->data);
meillo@10 105 if (hdr->id == HEAD_REPLY_TO) {
meillo@10 106 header *new_hdr = copy_header(hdr);
meillo@10 107 if (map_address_header
meillo@10 108 (new_hdr, route->map_h_reply_to_addresses)) {
meillo@10 109 hdr_node->data = new_hdr;
meillo@10 110 /* we need this list only to carefully free the extra headers: */
meillo@10 111 msgout->xtra_hdr_list = g_list_append(msgout->xtra_hdr_list, new_hdr);
meillo@10 112 } else
meillo@10 113 g_free(new_hdr);
meillo@10 114 }
meillo@10 115 }
meillo@10 116 } else {
meillo@10 117 /* replace Reply-to domain */
meillo@10 118 if (route->set_h_reply_to_domain != NULL) {
meillo@10 119 GList *hdr_node;
meillo@10 120
meillo@10 121 foreach(msgout->hdr_list, hdr_node) {
meillo@10 122 header *hdr = (header *) (hdr_node->data);
meillo@10 123 if (hdr->id == HEAD_REPLY_TO) {
meillo@10 124 header *new_hdr = copy_header(hdr);
meillo@10 125
meillo@10 126 set_address_header_domain(new_hdr, route-> set_h_reply_to_domain);
meillo@10 127 hdr_node->data = new_hdr;
meillo@10 128 /* we need this list only to carefully free the extra headers: */
meillo@10 129 msgout->xtra_hdr_list = g_list_append(msgout->xtra_hdr_list, new_hdr);
meillo@10 130 }
meillo@10 131 }
meillo@10 132 }
meillo@0 133 }
meillo@0 134
meillo@10 135 /* map Mail-Followup-To addresses */
meillo@10 136 if (route->map_h_mail_followup_to_addresses != NULL) {
meillo@10 137 GList *hdr_node;
meillo@10 138 foreach(msgout->hdr_list, hdr_node) {
meillo@10 139 header *hdr = (header *) (hdr_node->data);
meillo@10 140 if (strncasecmp(hdr->header, "Mail-Followup-To", 16) == 0) {
meillo@10 141 header *new_hdr = copy_header(hdr);
meillo@10 142 if (map_address_header(new_hdr, route->map_h_mail_followup_to_addresses)) {
meillo@10 143 hdr_node->data = new_hdr;
meillo@10 144 /* we need this list only to carefully free the extra headers: */
meillo@10 145 msgout->xtra_hdr_list = g_list_append(msgout->xtra_hdr_list, new_hdr);
meillo@10 146 } else
meillo@10 147 g_free(new_hdr);
meillo@10 148 }
meillo@10 149 }
meillo@10 150 }
meillo@0 151
meillo@10 152 /* set Sender: domain to return_path->domain */
meillo@10 153 if (route->expand_h_sender_domain) {
meillo@10 154 GList *hdr_node;
meillo@0 155
meillo@10 156 foreach(msgout->hdr_list, hdr_node) {
meillo@10 157 header *hdr = (header *) (hdr_node->data);
meillo@10 158 if (hdr->id == HEAD_SENDER) {
meillo@10 159 header *new_hdr = copy_header(hdr);
meillo@0 160
meillo@10 161 set_address_header_domain(new_hdr, msgout->return_path->domain);
meillo@10 162 hdr_node->data = new_hdr;
meillo@10 163 /* we need this list only to carefully free the extra headers: */
meillo@10 164 msgout->xtra_hdr_list = g_list_append(msgout->xtra_hdr_list, new_hdr);
meillo@10 165 }
meillo@10 166 }
meillo@10 167 }
meillo@0 168
meillo@10 169 /* set Sender: domain to return_path->domain */
meillo@10 170 if (route->expand_h_sender_address) {
meillo@10 171 GList *hdr_node;
meillo@0 172
meillo@10 173 foreach(msgout->hdr_list, hdr_node) {
meillo@10 174 header *hdr = (header *) (hdr_node->data);
meillo@10 175 if (hdr->id == HEAD_SENDER) {
meillo@10 176 header *new_hdr;
meillo@0 177
meillo@15 178 new_hdr = create_header(HEAD_SENDER, "Sender: %s@%s\n",
meillo@15 179 msgout->return_path->local_part, msgout->return_path->domain);
meillo@10 180 hdr_node->data = new_hdr;
meillo@10 181 /* we need this list only to carefully free the extra headers: */
meillo@10 182 msgout->xtra_hdr_list = g_list_append(msgout->xtra_hdr_list, new_hdr);
meillo@10 183 }
meillo@10 184 }
meillo@10 185 }
meillo@0 186
meillo@10 187 if (msgout->xtra_hdr_list == NULL) {
meillo@10 188 /* nothing was changed */
meillo@10 189 g_list_free(msgout->hdr_list);
meillo@10 190 msgout->hdr_list = NULL;
meillo@10 191 }
meillo@10 192 DEBUG(5) debugf("rewrite_headers() returning\n");
meillo@0 193 }
meillo@0 194
meillo@237 195 /*
meillo@367 196 ** Split a recipient list into the three groups:
meillo@367 197 ** - local recipients
meillo@367 198 ** - those maching the patterns
meillo@367 199 ** - those not matching the patterns
meillo@367 200 ** If patterns is NULL: only splitting between local and others is done.
meillo@237 201 */
meillo@10 202 void
meillo@367 203 split_rcpts(GList *rcpt_list, GList *patterns, GList **rl_local,
meillo@367 204 GList **rl_matching, GList **rl_others)
meillo@0 205 {
meillo@10 206 GList *rcpt_node;
meillo@373 207 GList *pat_node = NULL;
meillo@237 208 address *rcpt = NULL;
meillo@0 209
meillo@10 210 if (rcpt_list == NULL)
meillo@10 211 return;
meillo@0 212
meillo@10 213 foreach(rcpt_list, rcpt_node) {
meillo@237 214 rcpt = (address *) (rcpt_node->data);
meillo@373 215 pat_node = NULL;
meillo@0 216
meillo@237 217 if (addr_is_local(rcpt)) {
meillo@237 218 if (rl_local)
meillo@237 219 *rl_local = g_list_append(*rl_local, rcpt);
meillo@237 220 } else {
meillo@367 221 /*
meillo@373 222 ** if patterns is NULL, pat_node will be NULL,
meillo@367 223 ** hence all non-locals are put to others
meillo@367 224 */
meillo@373 225 foreach(patterns, pat_node) {
meillo@373 226 address *pat = (address *) (pat_node->data);
meillo@373 227 if (fnmatch(pat->domain, rcpt->domain, FNM_CASEFOLD)==0 && fnmatch(pat->local_part, rcpt->local_part, 0)==0) { /* TODO: match local_part caseless? */
meillo@237 228 break;
meillo@373 229 }
meillo@237 230 }
meillo@373 231 if (pat_node) {
meillo@355 232 if (rl_matching)
meillo@355 233 *rl_matching = g_list_append(*rl_matching, rcpt);
meillo@237 234 } else {
meillo@237 235 if (rl_others)
meillo@237 236 *rl_others = g_list_append(*rl_others, rcpt);
meillo@237 237 }
meillo@10 238 }
meillo@10 239 }
meillo@0 240 }
meillo@0 241
meillo@345 242 /*
meillo@367 243 ** Return a new list of the local rcpts in the rcpt_list
meillo@367 244 ** TODO: This function is almost exactly the same as remote_rcpts(). Merge?
meillo@345 245 */
meillo@345 246 GList*
meillo@366 247 local_rcpts(GList *rcpt_list)
meillo@345 248 {
meillo@345 249 GList *rcpt_node;
meillo@345 250 GList *local_rcpts = NULL;
meillo@345 251 address *rcpt = NULL;
meillo@345 252
meillo@345 253 if (!rcpt_list) {
meillo@345 254 return NULL;
meillo@345 255 }
meillo@345 256 foreach(rcpt_list, rcpt_node) {
meillo@345 257 rcpt = (address *) (rcpt_node->data);
meillo@345 258 if (addr_is_local(rcpt)) {
meillo@345 259 local_rcpts = g_list_append(local_rcpts, rcpt);
meillo@345 260 }
meillo@345 261 }
meillo@345 262 return local_rcpts;
meillo@345 263 }
meillo@345 264
meillo@345 265 /*
meillo@367 266 ** Return a new list of non-local rcpts in the rcpt_list
meillo@367 267 ** TODO: This function is almost exactly the same as local_rcpts(). Merge?
meillo@345 268 */
meillo@345 269 GList*
meillo@366 270 remote_rcpts(GList *rcpt_list)
meillo@345 271 {
meillo@345 272 GList *rcpt_node;
meillo@345 273 GList *remote_rcpts = NULL;
meillo@345 274 address *rcpt = NULL;
meillo@345 275
meillo@345 276 if (!rcpt_list) {
meillo@345 277 return NULL;
meillo@345 278 }
meillo@345 279 foreach(rcpt_list, rcpt_node) {
meillo@345 280 rcpt = (address *) (rcpt_node->data);
meillo@345 281 if (!addr_is_local(rcpt)) {
meillo@345 282 remote_rcpts = g_list_append(remote_rcpts, rcpt);
meillo@345 283 }
meillo@345 284 }
meillo@345 285 return remote_rcpts;
meillo@345 286 }
meillo@345 287
meillo@10 288 static gint
meillo@317 289 _g_list_addrcmp(gconstpointer pattern, gconstpointer addr)
meillo@0 290 {
meillo@317 291 int res;
meillo@366 292 address *patternaddr = (address*) pattern;
meillo@366 293 address *stringaddr = (address*) addr;
meillo@317 294
meillo@317 295 DEBUG(6) debugf("_g_list_addrcmp: pattern `%s' `%s' on string `%s' `%s'\n",
meillo@317 296 patternaddr->local_part, patternaddr->domain,
meillo@317 297 stringaddr->local_part, stringaddr->domain);
meillo@317 298 /* TODO: check if we should match here dependent on caseless_matching */
meillo@317 299 res = fnmatch(patternaddr->local_part, stringaddr->local_part, 0);
meillo@317 300 if (res != 0) {
meillo@317 301 DEBUG(6) debugf("_g_list_addrcmp: ... failed on local_part\n");
meillo@317 302 return res;
meillo@317 303 }
meillo@317 304 res = fnmatch(patternaddr->domain, stringaddr->domain, FNM_CASEFOLD);
meillo@317 305 DEBUG(6) debugf("_g_list_addrcmp: ... %s\n", (res==0) ? "matched" : "failed on domain");
meillo@317 306 return res;
meillo@0 307 }
meillo@0 308
meillo@10 309 gboolean
meillo@366 310 route_sender_is_allowed(connect_route *route, address *ret_path)
meillo@0 311 {
meillo@317 312 if (route->denied_senders && g_list_find_custom(route->denied_senders, ret_path, _g_list_addrcmp)) {
meillo@317 313 return FALSE;
meillo@10 314 }
meillo@317 315 if (route->allowed_senders) {
meillo@317 316 if (g_list_find_custom(route->allowed_senders, ret_path, _g_list_addrcmp)) {
meillo@10 317 return TRUE;
meillo@10 318 } else {
meillo@10 319 return FALSE;
meillo@10 320 }
meillo@10 321 }
meillo@10 322 return TRUE;
meillo@0 323 }
meillo@0 324
meillo@10 325 /*
meillo@367 326 ** Make lists of matching/not matching rcpts.
meillo@367 327 ** Local domains are NOT regared here, these should be sorted out previously
meillo@0 328 */
meillo@10 329 void
meillo@366 330 route_split_rcpts(connect_route *route, GList *rcpt_list, GList **p_rcpt_list, GList **p_non_rcpt_list)
meillo@0 331 {
meillo@10 332 GList *tmp_list = NULL;
meillo@10 333 /* sort out those domains that can be sent over this connection: */
meillo@317 334 if (route->allowed_recipients) {
meillo@317 335 DEBUG(5) debugf("testing for route->allowed_recipients\n");
meillo@317 336 split_rcpts(rcpt_list, route->allowed_recipients, NULL, &tmp_list, p_non_rcpt_list);
meillo@10 337 } else {
meillo@317 338 DEBUG(5) debugf("route->allowed_recipients == NULL\n");
meillo@10 339 tmp_list = g_list_copy(rcpt_list);
meillo@10 340 }
meillo@0 341
meillo@10 342 /* sort out those domains that cannot be sent over this connection: */
meillo@317 343 split_rcpts(tmp_list, route->denied_recipients, NULL, p_non_rcpt_list, p_rcpt_list);
meillo@10 344 g_list_free(tmp_list);
meillo@0 345 }
meillo@0 346
meillo@10 347 msg_out*
meillo@366 348 route_prepare_msgout(connect_route *route, msg_out *msgout)
meillo@0 349 {
meillo@10 350 message *msg = msgout->msg;
meillo@10 351 GList *rcpt_list = msgout->rcpt_list;
meillo@0 352
meillo@10 353 if (rcpt_list != NULL) {
meillo@10 354 /* found a few */
meillo@10 355 DEBUG(5) {
meillo@10 356 GList *node;
meillo@10 357 debugf("rcpts for routed delivery, route = %s, id = %s\n", route->name, msg->uid);
meillo@10 358 foreach(rcpt_list, node) {
meillo@10 359 address *rcpt = (address *) (node->data);
meillo@114 360 debugf(" rcpt for routed delivery: <%s@%s>\n",
meillo@114 361 rcpt->local_part, rcpt->domain);
meillo@10 362 }
meillo@10 363 }
meillo@0 364
meillo@367 365 /*
meillo@367 366 ** rewrite return path if there is a table, use that
meillo@367 367 ** if an address is found and if it has a domain, use that
meillo@367 368 */
meillo@10 369 if (route->map_return_path_addresses) {
meillo@10 370 address *ret_path = NULL;
meillo@10 371 DEBUG(5) debugf("looking up %s in map_return_path_addresses\n", msg->return_path->local_part);
meillo@10 372 ret_path = (address *) table_find_fnmatch(route->map_return_path_addresses, msg->return_path->local_part);
meillo@10 373 if (ret_path) {
meillo@10 374 DEBUG(5) debugf("found <%s@%s>\n", ret_path->local_part, ret_path->domain);
meillo@10 375 if (ret_path->domain == NULL)
meillo@10 376 ret_path->domain = route->set_return_path_domain
meillo@10 377 ? route->set_return_path_domain
meillo@10 378 : msg->return_path->domain;
meillo@10 379 msgout->return_path = copy_address(ret_path);
meillo@10 380 }
meillo@10 381 }
meillo@10 382 if (msgout->return_path == NULL) {
meillo@10 383 DEBUG(5) debugf("setting return path to %s\n", route->set_return_path_domain);
meillo@10 384 msgout->return_path = copy_modify_address(msg->return_path, NULL, route->set_return_path_domain);
meillo@10 385 }
meillo@10 386 rewrite_headers(msgout, route);
meillo@10 387
meillo@10 388 return msgout;
meillo@10 389 }
meillo@10 390 return NULL;
meillo@0 391 }
meillo@0 392
meillo@367 393 /*
meillo@367 394 ** put msgout's is msgout_list into bins (msgout_perhost structs) for each
meillo@367 395 ** host. Used if there is no mail_host.
meillo@367 396 ** route param is not used, we leave it here because that may change.
meillo@367 397 */
meillo@10 398 GList*
meillo@366 399 route_msgout_list(connect_route *route, GList *msgout_list)
meillo@0 400 {
meillo@10 401 GList *mo_ph_list = NULL;
meillo@10 402 GList *msgout_node;
meillo@0 403
meillo@10 404 foreach(msgout_list, msgout_node) {
meillo@10 405 msg_out *msgout = (msg_out *) (msgout_node->data);
meillo@10 406 msg_out *msgout_new;
meillo@10 407 GList *rcpt_list = msgout->rcpt_list;
meillo@10 408 GList *rcpt_node;
meillo@0 409
meillo@10 410 foreach(rcpt_list, rcpt_node) {
meillo@10 411 address *rcpt = rcpt_node->data;
meillo@10 412 msgout_perhost *mo_ph = NULL;
meillo@10 413 GList *mo_ph_node = NULL;
meillo@0 414
meillo@10 415 /* search host in mo_ph_list */
meillo@10 416 foreach(mo_ph_list, mo_ph_node) {
meillo@10 417 mo_ph = (msgout_perhost *) (mo_ph_node->data);
meillo@10 418 if (strcasecmp(mo_ph->host, rcpt->domain) == 0)
meillo@10 419 break;
meillo@10 420 }
meillo@10 421 if (mo_ph_node != NULL) {
meillo@10 422 /* there is already a rcpt for this host */
meillo@10 423 msg_out *msgout_last = (msg_out *) ((g_list_last(mo_ph->msgout_list))->data);
meillo@10 424 if (msgout_last->msg == msgout->msg) {
meillo@367 425 /*
meillo@367 426 ** if it is also the same message,
meillo@367 427 ** it must be the last one
meillo@367 428 ** appended to mo_ph->msgout_list
meillo@367 429 ** (since outer loop goes through
meillo@367 430 ** msgout_list)
meillo@367 431 */
meillo@10 432 msgout_last->rcpt_list = g_list_append(msgout_last->rcpt_list, rcpt);
meillo@10 433 } else {
meillo@10 434 /* if not, we append a new msgout */
meillo@10 435 /* make a copy of msgout */
meillo@10 436 msgout_new = create_msg_out(msgout->msg);
meillo@10 437 msgout_new->return_path = msgout->return_path;
meillo@10 438 msgout_new->hdr_list = msgout->hdr_list;
meillo@0 439
meillo@10 440 /* append our rcpt to it */
meillo@10 441 /* It is the 1st rcpt for this msg to this host, therefore we safely give NULL */
meillo@10 442 msgout_new->rcpt_list = g_list_append(NULL, rcpt);
meillo@10 443 mo_ph->msgout_list = g_list_append(mo_ph->msgout_list, msgout_new);
meillo@10 444 }
meillo@10 445 } else {
meillo@10 446 /* this rcpt to goes to another host */
meillo@10 447 mo_ph = create_msgout_perhost(rcpt->domain);
meillo@10 448 mo_ph_list = g_list_append(mo_ph_list, mo_ph);
meillo@0 449
meillo@10 450 /* make a copy of msgout */
meillo@10 451 msgout_new = create_msg_out(msgout->msg);
meillo@10 452 msgout_new->return_path = msgout->return_path;
meillo@10 453 msgout_new->hdr_list = msgout->hdr_list;
meillo@0 454
meillo@10 455 /* append our rcpt to it */
meillo@10 456 /* It is the 1st rcpt for this msg to this host, therefore we safely give NULL */
meillo@10 457 msgout_new->rcpt_list = g_list_append(NULL, rcpt);
meillo@10 458 mo_ph->msgout_list = g_list_append(mo_ph->msgout_list, msgout_new);
meillo@10 459 } /* if mo_ph != NULL */
meillo@10 460 } /* foreach(rcpt_list, ... */
meillo@10 461 } /* foreach(msgout_list, ... */
meillo@10 462
meillo@10 463 return mo_ph_list;
meillo@0 464 }