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