From 59e82a2a0e97f55f0e27323112116e01a06cb198 Mon Sep 17 00:00:00 2001 From: Philip Hazel Date: Tue, 7 Feb 2006 14:05:17 +0000 Subject: [PATCH] Improve queryprogram behaviour when not running as root. --- doc/doc-txt/ChangeLog | 16 +++++++- src/src/child.c | 59 +++++++++++++++++++--------- src/src/routers/queryprogram.c | 25 ++++++++++-- test/confs/5204 | 5 ++- test/log/0433 | 6 +-- test/scripts/5200-queryprogram/5204 | 8 +++- test/stderr/0433 | 12 +++--- test/stderr/5204 | 61 +++++++++++++++++++++++++---- test/stdout/5204 | 13 ++++++ 9 files changed, 164 insertions(+), 41 deletions(-) diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog index 212ec706c..103463fc1 100644 --- a/doc/doc-txt/ChangeLog +++ b/doc/doc-txt/ChangeLog @@ -1,4 +1,4 @@ -$Cambridge: exim/doc/doc-txt/ChangeLog,v 1.283 2006/02/03 15:26:54 ph10 Exp $ +$Cambridge: exim/doc/doc-txt/ChangeLog,v 1.284 2006/02/07 14:05:17 ph10 Exp $ Change log file for Exim from version 4.21 ------------------------------------------- @@ -72,6 +72,20 @@ PH/13 Domain literals for IPv6 were not recognized unless IPv6 support was recognized, so I have changed this. It also means that IPv4 domain literals of the form [IPV4:n.n.n.n] are now always recognized. +PH/14 When a uid/gid is specified for the queryprogram router, it cannot be + used if the router is not running as root, for example, when verifying at + ACL time, or when using -bh. The debugging output from this situation was + non-existent - all you got was a failure to exec. I have made two + changes: + + (a) Failures to set uid/gid, the current directory, or a process leader + in a subprocess such as that created by queryprogram now generate + suitable debugging ouput when -d is set. + + (b) The queryprogram router detects when it is not running as root, + outputs suitable debugging information if -d is set, and then runs + the subprocess without attempting to change uid/gid. + Exim version 4.60 ----------------- diff --git a/src/src/child.c b/src/src/child.c index 8f2f9d78c..4a1a89887 100644 --- a/src/src/child.c +++ b/src/src/child.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/child.c,v 1.7 2006/02/07 11:19:00 ph10 Exp $ */ +/* $Cambridge: exim/src/src/child.c,v 1.8 2006/02/07 14:05:17 ph10 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -292,14 +292,48 @@ otherwise. Save the old state for resetting on the wait. */ oldsignal = signal(SIGCHLD, SIG_DFL); pid = fork(); -/* The child process becomes a process group leader if requested, and then -organizes the pipes. Any unexpected failure is signalled with EX_EXECFAILED; -these are all "should never occur" failures, except perhaps for exec failing -because the command doesn't exist. */ +/* Handle the child process. First, set the required environment. We must do +this before messing with the pipes, in order to be able to write debugging +output when things go wrong. */ if (pid == 0) { - if (make_leader && setpgid(0,0) < 0) goto CHILD_FAILED; + signal(SIGUSR1, SIG_IGN); + + if (newgid != NULL && setgid(*newgid) < 0) + { + DEBUG(D_any) debug_printf("failed to set gid=%ld in subprocess: %s\n", + (long int)(*newgid), strerror(errno)); + goto CHILD_FAILED; + } + + if (newuid != NULL && setuid(*newuid) < 0) + { + DEBUG(D_any) debug_printf("failed to set uid=%ld in subprocess: %s\n", + (long int)(*newuid), strerror(errno)); + goto CHILD_FAILED; + } + + (void)umask(newumask); + + if (wd != NULL && Uchdir(wd) < 0) + { + DEBUG(D_any) debug_printf("failed to chdir to %s: %s\n", wd, + strerror(errno)); + goto CHILD_FAILED; + } + + /* Becomes a process group leader if requested, and then organize the pipes. + Any unexpected failure is signalled with EX_EXECFAILED; these are all "should + never occur" failures, except for exec failing because the command doesn't + exist. */ + + if (make_leader && setpgid(0,0) < 0) + { + DEBUG(D_any) debug_printf("failed to set group leader in subprocess: %s\n", + strerror(errno)); + goto CHILD_FAILED; + } (void)close(inpfd[pipe_write]); force_fd(inpfd[pipe_read], 0); @@ -310,17 +344,6 @@ if (pid == 0) (void)close(2); (void)dup2(1, 2); - /* Set the required environment. */ - - signal(SIGUSR1, SIG_IGN); - if (newgid != NULL && setgid(*newgid) < 0) goto CHILD_FAILED; - if (newuid != NULL && setuid(*newuid) < 0) goto CHILD_FAILED; - (void)umask(newumask); - - /* Set the working directory if required */ - - if (wd != NULL && Uchdir(wd) < 0) goto CHILD_FAILED; - /* Now do the exec */ if (envp == NULL) execv(CS argv[0], (char *const *)argv); @@ -328,7 +351,7 @@ if (pid == 0) /* Failed to execv. Signal this failure using EX_EXECFAILED. We are losing the actual errno we got back, because there is no way to return - this. */ + this information. */ CHILD_FAILED: _exit(EX_EXECFAILED); /* Note: must be _exit(), NOT exit() */ diff --git a/src/src/routers/queryprogram.c b/src/src/routers/queryprogram.c index c9b409e78..1b974fc4e 100644 --- a/src/src/routers/queryprogram.c +++ b/src/src/routers/queryprogram.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/routers/queryprogram.c,v 1.7 2006/02/07 11:19:02 ph10 Exp $ */ +/* $Cambridge: exim/src/src/routers/queryprogram.c,v 1.8 2006/02/07 14:05:17 ph10 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -198,8 +198,12 @@ queryprogram_router_options_block *ob = (queryprogram_router_options_block *)(rblock->options_block); uschar *current_directory = ob->current_directory; ugid_block ugid; +uid_t curr_uid = getuid(); +gid_t curr_gid = getgid(); uid_t uid = ob->cmd_uid; gid_t gid = ob->cmd_gid; +uid_t *puid = &uid; +gid_t *pgid = &gid; DEBUG(D_route) debug_printf("%s router called for %s: domain = %s\n", rblock->name, addr->address, addr->domain); @@ -250,9 +254,24 @@ if (!ob->cmd_gid_set) } } -DEBUG(D_route) debug_printf("uid=%ld gid=%ld current_directory=%s\n", +DEBUG(D_route) debug_printf("requires uid=%ld gid=%ld current_directory=%s\n", (long int)uid, (long int)gid, current_directory); +/* If we are not running as root, we will not be able to change uid/gid. */ + +if (curr_uid != root_uid && (uid != curr_uid || gid != curr_gid)) + { + DEBUG(D_route) + { + debug_printf("not running as root: cannot change uid/gid\n"); + debug_printf("subprocess will run with uid=%ld gid=%ld\n", + (long int)curr_uid, (long int)curr_gid); + } + puid = pgid = NULL; + } + +/* Set up the command to run */ + if (!transport_set_up_command(&argvptr, /* anchor for arg list */ ob->command, /* raw command */ TRUE, /* expand the arguments */ @@ -266,7 +285,7 @@ if (!transport_set_up_command(&argvptr, /* anchor for arg list */ /* Create the child process, making it a group leader. */ -pid = child_open_uid(argvptr, NULL, 0077, &uid, &gid, &fd_in, &fd_out, +pid = child_open_uid(argvptr, NULL, 0077, puid, pgid, &fd_in, &fd_out, current_directory, TRUE); if (pid < 0) diff --git a/test/confs/5204 b/test/confs/5204 index a134270a6..5846f5b11 100644 --- a/test/confs/5204 +++ b/test/confs/5204 @@ -1,5 +1,7 @@ # Exim test configuration 5204 +COMMAND_USER=EXIMUSER + exim_path = EXIM_PATH host_lookup_order = bydns primary_hostname = myhost.test.ex @@ -11,6 +13,7 @@ gecos_name = CALLER_NAME # ----- Main settings ----- +acl_smtp_rcpt = accept verify = recipient domainlist local_domains = test.ex # ----- Routers ----- @@ -27,7 +30,7 @@ q: caseful_local_part command = /bin/echo \ "${if match{$local_part}{^\"([^\"]*)\"(.*)\\$}{$1$2}{$local_part}}" - command_user = EXIMUSER + command_user = COMMAND_USER domains = ! +local_domains no_more transport = smtp diff --git a/test/log/0433 b/test/log/0433 index fbe0f37e6..9a80732d5 100644 --- a/test/log/0433 +++ b/test/log/0433 @@ -1,6 +1,6 @@ 1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 -1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 port 1226 (IPv4) +1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 port 1226 1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on [127.0.0.1]:1228 port 1225 (IPv4) port 1226 (IPv4) -1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 port 1226 (IPv4) [127.0.0.1]:1228 +1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 port 1226 [127.0.0.1]:1228 1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on [127.0.0.1]:1228 port 1227 (IPv4) -1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 port 1226 (IPv4) +1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 port 1226 diff --git a/test/scripts/5200-queryprogram/5204 b/test/scripts/5200-queryprogram/5204 index 593ce82eb..2509091f9 100644 --- a/test/scripts/5200-queryprogram/5204 +++ b/test/scripts/5200-queryprogram/5204 @@ -1,4 +1,4 @@ -# queryprogram +# queryprogram general tests 1 exim -bt '"ACCEPT hosts=localhost lookup=byname"@some.host' **** @@ -26,3 +26,9 @@ exim -d-all+route -bt '"REDIRECT postmaster@test.ex"@some.host' exim -odi '"FAIL cannot route this one (FAIL)"@some.host' Test message **** +exim -DCOMMAND_USER=CALLER -d-all+route -bh V4NET.2.3.4 +ehlo some.name +mail from: +rcpt to:<"FAIL cannot route this one (FAIL)"@some.host> +quit +**** diff --git a/test/stderr/0433 b/test/stderr/0433 index 12d4d80f1..bb822dd35 100644 --- a/test/stderr/0433 +++ b/test/stderr/0433 @@ -27,8 +27,8 @@ pid written to TESTSUITE/spool/exim-daemon.pid changed uid/gid: running as a daemon uid=EXIM_UID gid=EXIM_GID pid=pppp LOG: MAIN - exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 port 1226 (IPv4) -set_process_info: pppp daemon: no queue runs, listening for SMTP on port 1225 port 1226 (IPv4) + exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 port 1226 +set_process_info: pppp daemon: no queue runs, listening for SMTP on port 1225 port 1226 daemon running with uid=EXIM_UID gid=EXIM_GID euid=EXIM_UID egid=EXIM_GID Listening... Exim version x.yz .... @@ -61,8 +61,8 @@ pid written to TESTSUITE/spool/exim-daemon.pid changed uid/gid: running as a daemon uid=EXIM_UID gid=EXIM_GID pid=pppp LOG: MAIN - exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 port 1226 (IPv4) [127.0.0.1]:1228 -set_process_info: pppp daemon: no queue runs, listening for SMTP on port 1225 port 1226 (IPv4) [127.0.0.1]:1228 + exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 port 1226 [127.0.0.1]:1228 +set_process_info: pppp daemon: no queue runs, listening for SMTP on port 1225 port 1226 [127.0.0.1]:1228 daemon running with uid=EXIM_UID gid=EXIM_GID euid=EXIM_UID egid=EXIM_GID Listening... Exim version x.yz .... @@ -97,7 +97,7 @@ pid written to TESTSUITE/spool/exim-daemon.pid changed uid/gid: running as a daemon uid=EXIM_UID gid=EXIM_GID pid=pppp LOG: MAIN - exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 port 1226 (IPv4) -set_process_info: pppp daemon: no queue runs, listening for SMTP on port 1225 port 1226 (IPv4) + exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 port 1226 +set_process_info: pppp daemon: no queue runs, listening for SMTP on port 1225 port 1226 daemon running with uid=EXIM_UID gid=EXIM_GID euid=EXIM_UID egid=EXIM_GID Listening... diff --git a/test/stderr/5204 b/test/stderr/5204 index f8ef7c9df..c8aa99a4d 100644 --- a/test/stderr/5204 +++ b/test/stderr/5204 @@ -14,7 +14,7 @@ local_part=DECLINE cannot route this one (DECLINE) domain=some.host checking domains calling q router q router called for "DECLINE cannot route this one (DECLINE)"@some.host: domain = some.host -uid=EXIM_UID gid=EXIM_GID current_directory=/ +requires uid=EXIM_UID gid=EXIM_GID current_directory=/ command wrote: DECLINE cannot route this one (DECLINE) q router declined for "DECLINE cannot route this one (DECLINE)"@some.host "more" is false: skipping remaining routers @@ -34,7 +34,7 @@ local_part=FAIL cannot route this one (FAIL) domain=some.host checking domains calling q router q router called for "FAIL cannot route this one (FAIL)"@some.host: domain = some.host -uid=EXIM_UID gid=EXIM_GID current_directory=/ +requires uid=EXIM_UID gid=EXIM_GID current_directory=/ command wrote: FAIL cannot route this one (FAIL) q router forced address failure >>>>>>>>>>>>>>>> Exim pid=pppp terminating with rc=2 >>>>>>>>>>>>>>>> @@ -52,7 +52,7 @@ local_part=ERROR cannot route this one (ERROR) domain=some.host checking domains calling q router q router called for "ERROR cannot route this one (ERROR)"@some.host: domain = some.host -uid=EXIM_UID gid=EXIM_GID current_directory=/ +requires uid=EXIM_UID gid=EXIM_GID current_directory=/ command wrote: ERROR cannot route this one (ERROR) LOG: PANIC q router: bad command yield: ERROR cannot route this one (ERROR) @@ -73,7 +73,7 @@ local_part=DEFER cannot route this one (DEFER) domain=some.host checking domains calling q router q router called for "DEFER cannot route this one (DEFER)"@some.host: domain = some.host -uid=EXIM_UID gid=EXIM_GID current_directory=/ +requires uid=EXIM_UID gid=EXIM_GID current_directory=/ command wrote: DEFER cannot route this one (DEFER) q router: defer for "DEFER cannot route this one (DEFER)"@some.host message: cannot route this one (DEFER) @@ -92,7 +92,7 @@ local_part=ACCEPT transport = other_smtp hosts=ten-1.test.ex domain=some.host checking domains calling q router q router called for "ACCEPT transport = other_smtp hosts=ten-1.test.ex"@some.host: domain = some.host -uid=EXIM_UID gid=EXIM_GID current_directory=/ +requires uid=EXIM_UID gid=EXIM_GID current_directory=/ command wrote: ACCEPT transport = other_smtp hosts=ten-1.test.ex finding IP address for ten-1.test.ex doing DNS lookup @@ -119,7 +119,7 @@ local_part=PASS domain=some.host checking domains calling q router q router called for PASS@some.host: domain = some.host -uid=EXIM_UID gid=EXIM_GID current_directory=/ +requires uid=EXIM_UID gid=EXIM_GID current_directory=/ command wrote: PASS q router passed for PASS@some.host --------> s router <-------- @@ -158,7 +158,7 @@ local_part=FREEZE cannot route this one (FREEZE) domain=some.host checking domains calling q router q router called for "FREEZE cannot route this one (FREEZE)"@some.host: domain = some.host -uid=EXIM_UID gid=EXIM_GID current_directory=/ +requires uid=EXIM_UID gid=EXIM_GID current_directory=/ command wrote: FREEZE cannot route this one (FREEZE) q router: defer for "FREEZE cannot route this one (FREEZE)"@some.host message: cannot route this one (FREEZE) @@ -177,7 +177,7 @@ local_part=REDIRECT postmaster@test.ex domain=some.host checking domains calling q router q router called for "REDIRECT postmaster@test.ex"@some.host: domain = some.host -uid=EXIM_UID gid=EXIM_GID current_directory=/ +requires uid=EXIM_UID gid=EXIM_GID current_directory=/ command wrote: REDIRECT postmaster@test.ex rda_interpret (string): postmaster@test.ex expanded: postmaster@test.ex @@ -217,3 +217,48 @@ routed by pm router envelope to: postmaster@test.ex transport: null >>>>>>>>>>>>>>>> Exim pid=pppp terminating with rc=0 >>>>>>>>>>>>>>>> +Exim version x.yz .... +configuration file is TESTSUITE/test-config +admin user +host in hosts_connection_nolog? no (option unset) +LOG: smtp_connection MAIN + SMTP connection from [V4NET.2.3.4] +host in host_lookup? no (option unset) +host in host_reject_connection? no (option unset) +host in sender_unqualified_hosts? no (option unset) +host in recipient_unqualified_hosts? no (option unset) +host in helo_verify_hosts? no (option unset) +host in helo_try_verify_hosts? no (option unset) +host in helo_accept_junk_hosts? no (option unset) +some.name in helo_lookup_domains? no (end of list) +host in pipelining_advertise_hosts? yes (matched "*") +processing "accept" +check verify = recipient +>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +routing "FAIL cannot route this one (FAIL)"@some.host +--------> b router <-------- +local_part=fail cannot route this one (fail) domain=some.host +checking senders +y in ""? no (end of list) +x@y in ":"? no (end of list) +b router skipped: senders mismatch +--------> q router <-------- +local_part=FAIL cannot route this one (FAIL) domain=some.host +checking domains +some.host in "test.ex"? no (end of list) +some.host in "! +local_domains"? yes (end of list) +calling q router +q router called for "FAIL cannot route this one (FAIL)"@some.host: domain = some.host +requires uid=CALLER_UID gid=CALLER_GID current_directory=/ +not running as root: cannot change uid/gid +subprocess will run with uid=EXIM_UID gid=EXIM_GID +command wrote: FAIL cannot route this one (FAIL) +q router forced address failure +----------- end verify ------------ +accept: condition test failed +end of inline ACL: implicit DENY +LOG: MAIN REJECT + H=(some.name) [V4NET.2.3.4] F= rejected RCPT <"FAIL cannot route this one (FAIL)"@some.host>: cannot route this one (FAIL) +LOG: smtp_connection MAIN + SMTP connection from (some.name) [V4NET.2.3.4] closed by QUIT +>>>>>>>>>>>>>>>> Exim pid=pppp terminating with rc=0 >>>>>>>>>>>>>>>> diff --git a/test/stdout/5204 b/test/stdout/5204 index df0810ede..52f9242b9 100644 --- a/test/stdout/5204 +++ b/test/stdout/5204 @@ -19,3 +19,16 @@ PASS@some.host postmaster@test.ex <-- "REDIRECT postmaster@test.ex"@some.host router = pm, transport = null + +**** SMTP testing session as if from host V4NET.2.3.4 +**** but without any ident (RFC 1413) callback. +**** This is not for real! + +220 myhost.test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000 +250-myhost.test.ex Hello some.name [V4NET.2.3.4] +250-SIZE 52428800 +250-PIPELINING +250 HELP +250 OK +550 cannot route this one (FAIL) +221 myhost.test.ex closing connection -- 2.30.2