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