| 1 | /* $Cambridge: exim/src/src/routers/queryprogram.c,v 1.2 2005/01/04 10:00:44 ph10 Exp $ */ |
| 2 | |
| 3 | /************************************************* |
| 4 | * Exim - an Internet mail transport agent * |
| 5 | *************************************************/ |
| 6 | |
| 7 | /* Copyright (c) University of Cambridge 1995 - 2005 */ |
| 8 | /* See the file NOTICE for conditions of use and distribution. */ |
| 9 | |
| 10 | #include "../exim.h" |
| 11 | #include "rf_functions.h" |
| 12 | #include "queryprogram.h" |
| 13 | |
| 14 | |
| 15 | |
| 16 | /* Options specific to the queryprogram router. */ |
| 17 | |
| 18 | optionlist queryprogram_router_options[] = { |
| 19 | { "*expand_command_group", opt_bool | opt_hidden, |
| 20 | (void *)(offsetof(queryprogram_router_options_block, expand_cmd_gid)) }, |
| 21 | { "*expand_command_user", opt_bool | opt_hidden, |
| 22 | (void *)(offsetof(queryprogram_router_options_block, expand_cmd_uid)) }, |
| 23 | { "*set_command_group", opt_bool | opt_hidden, |
| 24 | (void *)(offsetof(queryprogram_router_options_block, cmd_gid_set)) }, |
| 25 | { "*set_command_user", opt_bool | opt_hidden, |
| 26 | (void *)(offsetof(queryprogram_router_options_block, cmd_uid_set)) }, |
| 27 | { "command", opt_stringptr, |
| 28 | (void *)(offsetof(queryprogram_router_options_block, command)) }, |
| 29 | { "command_group",opt_expand_gid, |
| 30 | (void *)(offsetof(queryprogram_router_options_block, cmd_gid)) }, |
| 31 | { "command_user", opt_expand_uid, |
| 32 | (void *)(offsetof(queryprogram_router_options_block, cmd_uid)) }, |
| 33 | { "current_directory", opt_stringptr, |
| 34 | (void *)(offsetof(queryprogram_router_options_block, current_directory)) }, |
| 35 | { "timeout", opt_time, |
| 36 | (void *)(offsetof(queryprogram_router_options_block, timeout)) } |
| 37 | }; |
| 38 | |
| 39 | /* Size of the options list. An extern variable has to be used so that its |
| 40 | address can appear in the tables drtables.c. */ |
| 41 | |
| 42 | int queryprogram_router_options_count = |
| 43 | sizeof(queryprogram_router_options)/sizeof(optionlist); |
| 44 | |
| 45 | /* Default private options block for the queryprogram router. */ |
| 46 | |
| 47 | queryprogram_router_options_block queryprogram_router_option_defaults = { |
| 48 | NULL, /* command */ |
| 49 | 60*60, /* timeout */ |
| 50 | (uid_t)(-1), /* cmd_uid */ |
| 51 | (gid_t)(-1), /* cmd_gid */ |
| 52 | FALSE, /* cmd_uid_set */ |
| 53 | FALSE, /* cmd_gid_set */ |
| 54 | US"/", /* current_directory */ |
| 55 | NULL, /* expand_cmd_gid */ |
| 56 | NULL /* expand_cmd_uid */ |
| 57 | }; |
| 58 | |
| 59 | |
| 60 | |
| 61 | /************************************************* |
| 62 | * Initialization entry point * |
| 63 | *************************************************/ |
| 64 | |
| 65 | /* Called for each instance, after its options have been read, to enable |
| 66 | consistency checks to be done, or anything else that needs to be set up. */ |
| 67 | |
| 68 | void |
| 69 | queryprogram_router_init(router_instance *rblock) |
| 70 | { |
| 71 | queryprogram_router_options_block *ob = |
| 72 | (queryprogram_router_options_block *)(rblock->options_block); |
| 73 | |
| 74 | /* A command must be given */ |
| 75 | |
| 76 | if (ob->command == NULL) |
| 77 | log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n " |
| 78 | "a command specification is required", rblock->name); |
| 79 | |
| 80 | /* A uid/gid must be supplied */ |
| 81 | |
| 82 | if (!ob->cmd_uid_set && ob->expand_cmd_uid == NULL) |
| 83 | log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s router:\n " |
| 84 | "command_user must be specified", rblock->name); |
| 85 | } |
| 86 | |
| 87 | |
| 88 | |
| 89 | /************************************************* |
| 90 | * Process a set of generated new addresses * |
| 91 | *************************************************/ |
| 92 | |
| 93 | /* This function sets up a set of newly generated child addresses and puts them |
| 94 | on the new address chain. |
| 95 | |
| 96 | Arguments: |
| 97 | rblock router block |
| 98 | addr_new new address chain |
| 99 | addr original address |
| 100 | generated list of generated addresses |
| 101 | addr_prop the propagated data block, containing errors_to, |
| 102 | header change stuff, and address_data |
| 103 | |
| 104 | Returns: nothing |
| 105 | */ |
| 106 | |
| 107 | static void |
| 108 | add_generated(router_instance *rblock, address_item **addr_new, |
| 109 | address_item *addr, address_item *generated, |
| 110 | address_item_propagated *addr_prop) |
| 111 | { |
| 112 | while (generated != NULL) |
| 113 | { |
| 114 | address_item *next = generated; |
| 115 | generated = next->next; |
| 116 | |
| 117 | next->parent = addr; |
| 118 | orflag(next, addr, af_propagate); |
| 119 | next->p = *addr_prop; |
| 120 | next->start_router = rblock->redirect_router; |
| 121 | |
| 122 | next->next = *addr_new; |
| 123 | *addr_new = next; |
| 124 | |
| 125 | addr->child_count++; |
| 126 | |
| 127 | DEBUG(D_route) |
| 128 | debug_printf("%s router generated %s\n", rblock->name, next->address); |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | |
| 133 | |
| 134 | |
| 135 | /************************************************* |
| 136 | * Main entry point * |
| 137 | *************************************************/ |
| 138 | |
| 139 | /* See local README for interface details. This router returns: |
| 140 | |
| 141 | DECLINE |
| 142 | . DECLINE returned |
| 143 | . self = DECLINE |
| 144 | |
| 145 | PASS |
| 146 | . PASS returned |
| 147 | . timeout of host lookup and pass_on_timeout set |
| 148 | . self = PASS |
| 149 | |
| 150 | DEFER |
| 151 | . verifying the errors address caused a deferment or a big disaster such |
| 152 | as an expansion failure (rf_get_errors_address) |
| 153 | . expanding a headers_{add,remove} string caused a deferment or another |
| 154 | expansion error (rf_get_munge_headers) |
| 155 | . a problem in rf_get_transport: no transport when one is needed; |
| 156 | failed to expand dynamic transport; failed to find dynamic transport |
| 157 | . bad lookup type |
| 158 | . problem looking up host (rf_lookup_hostlist) |
| 159 | . self = DEFER or FREEZE |
| 160 | . failure to set up uid/gid for running the command |
| 161 | . failure of transport_set_up_command: too many arguments, expansion fail |
| 162 | . failure to create child process |
| 163 | . child process crashed or timed out or didn't return data |
| 164 | . :defer: in data |
| 165 | . DEFER or FREEZE returned |
| 166 | . problem in redirection data |
| 167 | . unknown transport name or trouble expanding router transport |
| 168 | |
| 169 | FAIL |
| 170 | . :fail: in data |
| 171 | . FAIL returned |
| 172 | . self = FAIL |
| 173 | |
| 174 | OK |
| 175 | . address added to addr_local or addr_remote for delivery |
| 176 | . new addresses added to addr_new |
| 177 | */ |
| 178 | |
| 179 | int |
| 180 | queryprogram_router_entry( |
| 181 | router_instance *rblock, /* data for this instantiation */ |
| 182 | address_item *addr, /* address we are working on */ |
| 183 | struct passwd *pw, /* passwd entry after check_local_user */ |
| 184 | BOOL verify, /* TRUE when verifying */ |
| 185 | address_item **addr_local, /* add it to this if it's local */ |
| 186 | address_item **addr_remote, /* add it to this if it's remote */ |
| 187 | address_item **addr_new, /* put new addresses on here */ |
| 188 | address_item **addr_succeed) /* put old address here on success */ |
| 189 | { |
| 190 | int fd_in, fd_out, len, rc; |
| 191 | pid_t pid; |
| 192 | struct passwd *upw = NULL; |
| 193 | uschar buffer[1024]; |
| 194 | uschar **argvptr; |
| 195 | uschar *rword, *rdata, *s; |
| 196 | address_item_propagated addr_prop; |
| 197 | queryprogram_router_options_block *ob = |
| 198 | (queryprogram_router_options_block *)(rblock->options_block); |
| 199 | uschar *current_directory = ob->current_directory; |
| 200 | ugid_block ugid; |
| 201 | uid_t uid = ob->cmd_uid; |
| 202 | gid_t gid = ob->cmd_gid; |
| 203 | |
| 204 | DEBUG(D_route) debug_printf("%s router called for %s: domain = %s\n", |
| 205 | rblock->name, addr->address, addr->domain); |
| 206 | |
| 207 | ugid.uid_set = ugid.gid_set = FALSE; |
| 208 | |
| 209 | /* Set up the propagated data block with the current address_data and the |
| 210 | errors address and extra header stuff. */ |
| 211 | |
| 212 | addr_prop.address_data = deliver_address_data; |
| 213 | |
| 214 | rc = rf_get_errors_address(addr, rblock, verify, &(addr_prop.errors_address)); |
| 215 | if (rc != OK) return rc; |
| 216 | |
| 217 | rc = rf_get_munge_headers(addr, rblock, &(addr_prop.extra_headers), |
| 218 | &(addr_prop.remove_headers)); |
| 219 | if (rc != OK) return rc; |
| 220 | |
| 221 | /* Get the fixed or expanded uid under which the command is to run |
| 222 | (initialization ensures that one or the other is set). */ |
| 223 | |
| 224 | if (!ob->cmd_uid_set) |
| 225 | { |
| 226 | if (!route_find_expanded_user(ob->expand_cmd_uid, rblock->name, US"router", |
| 227 | &upw, &uid, &(addr->message))) |
| 228 | return DEFER; |
| 229 | } |
| 230 | |
| 231 | /* Get the fixed or expanded gid, or take the gid from the passwd entry. */ |
| 232 | |
| 233 | if (!ob->cmd_gid_set) |
| 234 | { |
| 235 | if (ob->expand_cmd_gid != NULL) |
| 236 | { |
| 237 | if (route_find_expanded_group(ob->expand_cmd_gid, rblock->name, |
| 238 | US"router", &gid, &(addr->message))) |
| 239 | return DEFER; |
| 240 | } |
| 241 | else if (upw != NULL) |
| 242 | { |
| 243 | gid = upw->pw_gid; |
| 244 | } |
| 245 | else |
| 246 | { |
| 247 | addr->message = string_sprintf("command_user set without command_group " |
| 248 | "for %s router", rblock->name); |
| 249 | return DEFER; |
| 250 | } |
| 251 | } |
| 252 | |
| 253 | DEBUG(D_route) debug_printf("uid=%ld gid=%ld current_directory=%s\n", |
| 254 | (long int)uid, (long int)gid, current_directory); |
| 255 | |
| 256 | if (!transport_set_up_command(&argvptr, /* anchor for arg list */ |
| 257 | ob->command, /* raw command */ |
| 258 | TRUE, /* expand the arguments */ |
| 259 | 0, /* not relevant when... */ |
| 260 | NULL, /* no transporting address */ |
| 261 | US"queryprogram router", /* for error messages */ |
| 262 | &(addr->message))) /* where to put error message */ |
| 263 | { |
| 264 | return DEFER; |
| 265 | } |
| 266 | |
| 267 | /* Create the child process, making it a group leader. */ |
| 268 | |
| 269 | pid = child_open_uid(argvptr, NULL, 0077, &uid, &gid, &fd_in, &fd_out, |
| 270 | current_directory, TRUE); |
| 271 | |
| 272 | if (pid < 0) |
| 273 | { |
| 274 | addr->message = string_sprintf("%s router couldn't create child process: %s", |
| 275 | rblock->name, strerror(errno)); |
| 276 | return DEFER; |
| 277 | } |
| 278 | |
| 279 | /* Nothing is written to the standard input. */ |
| 280 | |
| 281 | close(fd_in); |
| 282 | |
| 283 | /* Wait for the process to finish, applying the timeout, and inspect its return |
| 284 | code. */ |
| 285 | |
| 286 | if ((rc = child_close(pid, ob->timeout)) != 0) |
| 287 | { |
| 288 | if (rc > 0) |
| 289 | addr->message = string_sprintf("%s router: command returned non-zero " |
| 290 | "code %d", rblock->name, rc); |
| 291 | |
| 292 | else if (rc == -256) |
| 293 | { |
| 294 | addr->message = string_sprintf("%s router: command timed out", |
| 295 | rblock->name); |
| 296 | killpg(pid, SIGKILL); /* Kill the whole process group */ |
| 297 | } |
| 298 | |
| 299 | else if (rc == -257) |
| 300 | addr->message = string_sprintf("%s router: wait() failed: %s", |
| 301 | rblock->name, strerror(errno)); |
| 302 | |
| 303 | else |
| 304 | addr->message = string_sprintf("%s router: command killed by signal %d", |
| 305 | rblock->name, -rc); |
| 306 | |
| 307 | return DEFER; |
| 308 | } |
| 309 | |
| 310 | /* Read the pipe to get the command's output, and then close it. */ |
| 311 | |
| 312 | len = read(fd_out, buffer, sizeof(buffer) - 1); |
| 313 | close(fd_out); |
| 314 | |
| 315 | /* Failure to return any data is an error. */ |
| 316 | |
| 317 | if (len <= 0) |
| 318 | { |
| 319 | addr->message = string_sprintf("%s router: command failed to return data", |
| 320 | rblock->name); |
| 321 | return DEFER; |
| 322 | } |
| 323 | |
| 324 | /* Get rid of leading and trailing white space, and pick off the first word of |
| 325 | the result. */ |
| 326 | |
| 327 | while (len > 0 && isspace(buffer[len-1])) len--; |
| 328 | buffer[len] = 0; |
| 329 | |
| 330 | DEBUG(D_route) debug_printf("command wrote: %s\n", buffer); |
| 331 | |
| 332 | rword = buffer; |
| 333 | while (isspace(*rword)) rword++; |
| 334 | rdata = rword; |
| 335 | while (*rdata != 0 && !isspace(*rdata)) rdata++; |
| 336 | if (*rdata != 0) *rdata++ = 0; |
| 337 | |
| 338 | /* The word must be a known yield name. If it is "REDIRECT", the rest of the |
| 339 | line is redirection data, as for a .forward file. It may not contain filter |
| 340 | data, and it may not contain anything other than addresses (no files, no pipes, |
| 341 | no specials). */ |
| 342 | |
| 343 | if (strcmpic(rword, US"REDIRECT") == 0) |
| 344 | { |
| 345 | int filtertype; |
| 346 | redirect_block redirect; |
| 347 | address_item *generated = NULL; |
| 348 | |
| 349 | redirect.string = rdata; |
| 350 | redirect.isfile = FALSE; |
| 351 | |
| 352 | rc = rda_interpret(&redirect, /* redirection data */ |
| 353 | RDO_BLACKHOLE | /* forbid :blackhole: */ |
| 354 | RDO_FAIL | /* forbid :fail: */ |
| 355 | RDO_INCLUDE | /* forbid :include: */ |
| 356 | RDO_REWRITE, /* rewrite generated addresses */ |
| 357 | NULL, /* :include: directory not relevant */ |
| 358 | NULL, /* sieve vacation directory not relevant */ |
| 359 | &ugid, /* uid/gid (but not set) */ |
| 360 | &generated, /* where to hang the results */ |
| 361 | &(addr->message), /* where to put messages */ |
| 362 | NULL, /* don't skip syntax errors */ |
| 363 | &filtertype, /* not used; will always be FILTER_FORWARD */ |
| 364 | string_sprintf("%s router", rblock->name)); |
| 365 | |
| 366 | switch (rc) |
| 367 | { |
| 368 | /* FF_DEFER and FF_FAIL can arise only as a result of explicit commands. |
| 369 | If a configured message was supplied, allow it to be included in an SMTP |
| 370 | response after verifying. */ |
| 371 | |
| 372 | case FF_DEFER: |
| 373 | if (addr->message == NULL) addr->message = US"forced defer"; |
| 374 | else addr->user_message = addr->message; |
| 375 | return DEFER; |
| 376 | |
| 377 | case FF_FAIL: |
| 378 | add_generated(rblock, addr_new, addr, generated, &addr_prop); |
| 379 | if (addr->message == NULL) addr->message = US"forced rejection"; |
| 380 | else addr->user_message = addr->message; |
| 381 | return FAIL; |
| 382 | |
| 383 | case FF_DELIVERED: |
| 384 | break; |
| 385 | |
| 386 | case FF_NOTDELIVERED: /* an empty redirection list is bad */ |
| 387 | addr->message = US"no addresses supplied"; |
| 388 | /* Fall through */ |
| 389 | |
| 390 | case FF_ERROR: |
| 391 | default: |
| 392 | addr->basic_errno = ERRNO_BADREDIRECT; |
| 393 | addr->message = string_sprintf("error in redirect data: %s", addr->message); |
| 394 | return DEFER; |
| 395 | } |
| 396 | |
| 397 | /* Handle the generated addresses, if any. */ |
| 398 | |
| 399 | add_generated(rblock, addr_new, addr, generated, &addr_prop); |
| 400 | |
| 401 | /* Put the original address onto the succeed queue so that any retry items |
| 402 | that get attached to it get processed. */ |
| 403 | |
| 404 | addr->next = *addr_succeed; |
| 405 | *addr_succeed = addr; |
| 406 | |
| 407 | return OK; |
| 408 | } |
| 409 | |
| 410 | /* Handle other returns that are not ACCEPT */ |
| 411 | |
| 412 | if (strcmpic(rword, US"accept") != 0) |
| 413 | { |
| 414 | if (strcmpic(rword, US"decline") == 0) return DECLINE; |
| 415 | if (strcmpic(rword, US"pass") == 0) return PASS; |
| 416 | addr->message = string_copy(rdata); /* data is a message */ |
| 417 | if (strcmpic(rword, US"fail") == 0) return FAIL; |
| 418 | if (strcmpic(rword, US"freeze") == 0) addr->special_action = SPECIAL_FREEZE; |
| 419 | else if (strcmpic(rword, US"defer") != 0) |
| 420 | { |
| 421 | addr->message = string_sprintf("bad command yield: %s %s", rword, rdata); |
| 422 | log_write(0, LOG_PANIC, "%s router: %s", rblock->name, addr->message); |
| 423 | } |
| 424 | return DEFER; |
| 425 | } |
| 426 | |
| 427 | /* The command yielded "ACCEPT". The rest of the string is a number of keyed |
| 428 | fields from which we can fish out values using the "extract" expansion |
| 429 | function. To use this feature, we must put the string into the $value variable, |
| 430 | i.e. set lookup_value. */ |
| 431 | |
| 432 | lookup_value = rdata; |
| 433 | s = expand_string(US"${extract{data}{$value}}"); |
| 434 | if (*s != 0) addr_prop.address_data = string_copy(s); |
| 435 | |
| 436 | s = expand_string(US"${extract{transport}{$value}}"); |
| 437 | lookup_value = NULL; |
| 438 | |
| 439 | /* If we found a transport name, find the actual transport */ |
| 440 | |
| 441 | if (*s != 0) |
| 442 | { |
| 443 | transport_instance *transport; |
| 444 | for (transport = transports; transport != NULL; transport = transport->next) |
| 445 | if (Ustrcmp(transport->name, s) == 0) break; |
| 446 | if (transport == NULL) |
| 447 | { |
| 448 | addr->message = string_sprintf("unknown transport name %s yielded by " |
| 449 | "command", s); |
| 450 | log_write(0, LOG_PANIC, "%s router: %s", rblock->name, addr->message); |
| 451 | return DEFER; |
| 452 | } |
| 453 | addr->transport = transport; |
| 454 | } |
| 455 | |
| 456 | /* No transport given; get the transport from the router configuration. It may |
| 457 | be fixed or expanded, but there will be an error if it is unset, requested by |
| 458 | the last argument not being NULL. */ |
| 459 | |
| 460 | else |
| 461 | { |
| 462 | if (!rf_get_transport(rblock->transport_name, &(rblock->transport), addr, |
| 463 | rblock->name, US"transport")) |
| 464 | return DEFER; |
| 465 | addr->transport = rblock->transport; |
| 466 | } |
| 467 | |
| 468 | /* See if a host list is given, and if so, look up the addresses. */ |
| 469 | |
| 470 | lookup_value = rdata; |
| 471 | s = expand_string(US"${extract{hosts}{$value}}"); |
| 472 | |
| 473 | if (*s != 0) |
| 474 | { |
| 475 | int lookup_type = lk_default; |
| 476 | uschar *ss = expand_string(US"${extract{lookup}{$value}}"); |
| 477 | lookup_value = NULL; |
| 478 | |
| 479 | if (*ss != 0) |
| 480 | { |
| 481 | if (Ustrcmp(ss, "byname") == 0) lookup_type = lk_byname; |
| 482 | else if (Ustrcmp(ss, "bydns") == 0) lookup_type = lk_bydns; |
| 483 | else |
| 484 | { |
| 485 | addr->message = string_sprintf("bad lookup type \"%s\" yielded by " |
| 486 | "command", ss); |
| 487 | log_write(0, LOG_PANIC, "%s router: %s", rblock->name, addr->message); |
| 488 | return DEFER; |
| 489 | } |
| 490 | } |
| 491 | |
| 492 | host_build_hostlist(&(addr->host_list), s, FALSE); /* pro tem no randomize */ |
| 493 | |
| 494 | rc = rf_lookup_hostlist(rblock, addr, rblock->ignore_target_hosts, |
| 495 | lookup_type, hff_defer, addr_new); |
| 496 | if (rc != OK) return rc; |
| 497 | } |
| 498 | lookup_value = NULL; |
| 499 | |
| 500 | /* Put the errors address, extra headers, and address_data into this address */ |
| 501 | |
| 502 | addr->p = addr_prop; |
| 503 | |
| 504 | /* Queue the address for local or remote delivery. */ |
| 505 | |
| 506 | return rf_queue_add(addr, addr_local, addr_remote, rblock, pw)? |
| 507 | OK : DEFER; |
| 508 | } |
| 509 | |
| 510 | /* End of routers/queryprogram.c */ |