make-config.in: complete path (leftover of [807f64e2], 2015-12-26!)
[s-mailx.git] / privsep.c
blob8fae19209f999cd3eca012bcef0f0c1883ac011a
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Privilege-separated dot file lock program (OPT_DOTLOCK=yes)
3 *@ that is capable of calling setuid(2) and change its user identity
4 *@ to the configured VAL_PRIVSEP_USER (usually "root"), in order to create
5 *@ a dotlock file with the same UID/GID as the mailbox to be locked.
6 *@ It should be started when chdir(2)d to the lock file's directory,
7 *@ with a symlink-resolved target and with SIGPIPE being ignored.
9 * Copyright (c) 2015 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
10 * SPDX-License-Identifier: ISC
12 * Permission to use, copy, modify, and/or distribute this software for any
13 * purpose with or without fee is hereby granted, provided that the above
14 * copyright notice and this permission notice appear in all copies.
16 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
17 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
19 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
20 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
21 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
22 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24 #undef n_FILE
25 #define n_FILE privsep
26 #define n_PRIVSEP_SOURCE
28 #include "nail.h"
30 #if defined HAVE_PRCTL_DUMPABLE
31 # include <sys/prctl.h>
32 #elif defined HAVE_PTRACE_DENY
33 # include <sys/ptrace.h>
34 #elif defined HAVE_SETPFLAGS_PROTECT
35 # include <priv.h>
36 #endif
38 static void _ign_signal(int signum);
39 static uiz_t n_msleep(uiz_t millis, bool_t ignint);
41 #include "dotlock.h"
43 static void
44 _ign_signal(int signum){
45 struct sigaction nact, oact;
47 nact.sa_handler = SIG_IGN;
48 sigemptyset(&nact.sa_mask);
49 nact.sa_flags = 0;
50 sigaction(signum, &nact, &oact);
53 static uiz_t
54 n_msleep(uiz_t millis, bool_t ignint){
55 uiz_t rv;
57 #ifdef HAVE_NANOSLEEP
58 /* C99 */{
59 struct timespec ts, trem;
60 int i;
62 ts.tv_sec = millis / 1000;
63 ts.tv_nsec = (millis %= 1000) * 1000 * 1000;
65 while((i = nanosleep(&ts, &trem)) != 0 && ignint)
66 ts = trem;
67 rv = (i == 0) ? 0 : (trem.tv_sec * 1000) + (trem.tv_nsec / (1000 * 1000));
70 #elif defined HAVE_SLEEP
71 if((millis /= 1000) == 0)
72 millis = 1;
73 while((rv = sleep((unsigned int)millis)) != 0 && ignint)
74 millis = rv;
75 #else
76 # error Configuration should have detected a function for sleeping.
77 #endif
78 return rv;
81 int
82 main(int argc, char **argv){
83 char hostbuf[64];
84 struct n_dotlock_info di;
85 struct stat stb;
86 sigset_t nset, oset;
87 enum n_dotlock_state dls;
89 /* We're a dumb helper, ensure as much as we can noone else uses us */
90 if(argc != 12 ||
91 strcmp(argv[ 0], VAL_PRIVSEP) ||
92 (argv[1][0] != 'r' && argv[1][0] != 'w') ||
93 strcmp(argv[ 1] + 1, "dotlock") ||
94 strcmp(argv[ 2], "mailbox") ||
95 strchr(argv[ 3], '/') != NULL /* Seal path injection.. */ ||
96 strcmp(argv[ 4], "name") ||
97 strcmp(argv[ 6], "hostname") ||
98 strcmp(argv[ 8], "randstr") ||
99 strchr(argv[ 9], '/') != NULL /* ..attack vector */ ||
100 strcmp(argv[10], "pollmsecs") ||
101 fstat(STDIN_FILENO, &stb) == -1 || !S_ISFIFO(stb.st_mode) ||
102 fstat(STDOUT_FILENO, &stb) == -1 || !S_ISFIFO(stb.st_mode)){
103 jeuse:
104 fprintf(stderr,
105 "This is a helper program of " VAL_UAGENT " (in " VAL_BINDIR ").\n"
106 " It is capable of gaining more privileges than " VAL_UAGENT "\n"
107 " and will be used to create lock files.\n"
108 " The sole purpose is outsourcing of high privileges into\n"
109 " fewest lines of code in order to reduce attack surface.\n"
110 " This program cannot be run by itself.\n");
111 exit(n_EXIT_USE);
112 }else{
113 /* Prevent one more path injection attack vector, but be friendly */
114 char const *ccp;
115 size_t i;
116 char *cp, c;
118 for(ccp = argv[7], cp = hostbuf, i = 0; (c = *ccp) != '\0'; ++cp, ++ccp){
119 *cp = (c == '/' ? '_' : c);
120 if(++i == sizeof(hostbuf) -1)
121 break;
123 *cp = '\0';
124 if(cp == hostbuf)
125 goto jeuse;
126 argv[7] = hostbuf;
129 di.di_file_name = argv[3];
130 di.di_lock_name = argv[5];
131 di.di_hostname = argv[7];
132 di.di_randstr = argv[9];
133 di.di_pollmsecs = (size_t)strtoul(argv[11], NULL, 10);
135 /* Ensure the lock name and the file name are identical */
136 /* C99 */{
137 size_t i = strlen(di.di_file_name);
139 if(i == 0 || strncmp(di.di_file_name, di.di_lock_name, i) ||
140 di.di_lock_name[i] == '\0' || strcmp(di.di_lock_name + i, ".lock"))
141 goto jeuse;
144 close(STDERR_FILENO);
146 /* In order to prevent stale lock files at all cost block any signals until
147 * we have unlinked the lock file.
148 * It is still not safe because we may be SIGKILLed and may linger around
149 * because we have been SIGSTOPped, but unfortunately the standard doesn't
150 * give any option, e.g. atcrash() or open(O_TEMPORARY_KEEP_NAME) or so, ---
151 * and then again we should not unlink(2) the lock file unless our parent
152 * has finalized the synchronization! While at it, let me rant about the
153 * default action of realtime signals, program termination */
154 _ign_signal(SIGPIPE); /* (Inherited, though) */
155 sigfillset(&nset);
156 sigdelset(&nset, SIGCONT); /* (Rather redundant, though) */
157 sigprocmask(SIG_BLOCK, &nset, &oset);
159 dls = n_DLS_NOPERM | n_DLS_ABANDON;
161 /* First of all: we only dotlock when the executing user has the necessary
162 * rights to access the mailbox */
163 if(access(di.di_file_name, (argv[1][0] == 'r' ? R_OK : R_OK | W_OK)))
164 goto jmsg;
166 /* We need UID and GID information about the mailbox to lock */
167 if(stat(di.di_file_name, di.di_stb = &stb) == -1)
168 goto jmsg;
170 dls = n_DLS_PRIVFAILED | n_DLS_ABANDON;
172 /* We are SETUID and do not want to become traced or being attached to */
173 #if defined HAVE_PRCTL_DUMPABLE
174 if(prctl(PR_SET_DUMPABLE, 0))
175 goto jmsg;
176 #elif defined HAVE_PTRACE_DENY
177 if(ptrace(PT_DENY_ATTACH, 0, 0, 0) == -1)
178 goto jmsg;
179 #elif defined HAVE_SETPFLAGS_PROTECT
180 if(setpflags(__PROC_PROTECT, 1))
181 goto jmsg;
182 #endif
184 /* This privsep helper only gets executed when needed, it thus doesn't make
185 * sense to try to continue with initial privileges */
186 if(setuid(geteuid()))
187 goto jmsg;
189 dls = a_dotlock_create(&di);
191 /* Finally: notify our parent about the actual lock state.. */
192 jmsg:
193 write(STDOUT_FILENO, &dls, sizeof dls);
194 close(STDOUT_FILENO);
196 /* ..then eventually wait until we shall remove the lock again, which will
197 * be notified via the read returning */
198 if(dls == n_DLS_NONE){
199 read(STDIN_FILENO, &dls, sizeof dls);
201 unlink(di.di_lock_name);
204 sigprocmask(SIG_SETMASK, &oset, NULL);
205 return (dls == n_DLS_NONE ? n_EXIT_OK : n_EXIT_ERR);
208 /* s-it-mode */