1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Privilege-separated dot file lock program (WANT_DOTLOCK=yes)
3 *@ that is capable of calling setuid(2) and change its user identity
4 *@ to the configured 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 *@ and SIGPIPE should be ignored.
9 * Copyright (c) 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
11 * Permission to use, copy, modify, and/or distribute this software for any
12 * purpose with or without fee is hereby granted, provided that the above
13 * copyright notice and this permission notice appear in all copies.
15 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
16 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
17 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
18 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
20 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
21 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24 #define n_FILE privsep
25 #define n_PRIVSEP_SOURCE
31 static void _ign_signal(int signum
);
34 _ign_signal(int signum
)
36 struct sigaction nact
, oact
;
38 nact
.sa_handler
= SIG_IGN
;
39 sigemptyset(&nact
.sa_mask
);
41 sigaction(signum
, &nact
, &oact
);
45 main(int argc
, char **argv
)
47 struct dotlock_info di
;
50 enum dotlock_state dls
;
52 /* We're a dumb helper, ensure as much as we can noone else uses us */
54 strcmp(argv
[ 0], PRIVSEP
) ||
55 (argv
[1][0] != 'r' && argv
[1][0] != 'w') ||
56 strcmp(argv
[ 1] + 1, "dotlock") ||
57 strcmp(argv
[ 2], "mailbox") ||
58 strcmp(argv
[ 4], "name") ||
59 strcmp(argv
[ 6], "hostname") ||
60 strcmp(argv
[ 8], "randstr") ||
61 strcmp(argv
[10], "pollmsecs") ||
62 fstat(STDIN_FILENO
, &stb
) == -1 || !S_ISFIFO(stb
.st_mode
) ||
63 fstat(STDOUT_FILENO
, &stb
) == -1 || !S_ISFIFO(stb
.st_mode
)) {
66 "This is a helper program of \"" UAGENT
"\" (in " BINDIR
").\n"
67 " It is capable of gaining more privileges than \"" UAGENT
"\"\n"
68 " and will be used to create lock files.\n"
69 " It's sole purpose is outsourcing of high privileges into\n"
70 " fewest lines of code in order to reduce attack surface.\n"
71 " It cannot be run by itself.\n");
75 di
.di_file_name
= argv
[3];
76 di
.di_lock_name
= argv
[5];
77 di
.di_hostname
= argv
[7];
78 di
.di_randstr
= argv
[9];
79 di
.di_pollmsecs
= (size_t)strtoul(argv
[11], NULL
, 10);
81 size_t i
= strlen(di
.di_file_name
);
83 if (i
== 0 || strncmp(di
.di_file_name
, di
.di_lock_name
, i
) ||
84 di
.di_lock_name
[i
] == '\0' || strcmp(di
.di_lock_name
+ i
, ".lock"))
90 /* In order to prevent stale lock files at all cost block any signals until
91 * we have unlinked the lock file.
92 * It is still not safe because we may be SIGKILLed and may linger around
93 * because we have been SIGSTOPped, but unfortunately the standard doesn't
94 * give any option, e.g. atcrash() or open(O_TEMPORARY_KEEP_NAME) or so, ---
95 * and then again we should not unlink(2) the lock file unless our parent
96 * has finalized the synchronization! While at it, let me rant about the
97 * default action of realtime signals, program termination */
98 _ign_signal(SIGPIPE
); /* (Inherited, though) */
100 sigdelset(&nset
, SIGCONT
); /* (Rather redundant, though) */
101 sigprocmask(SIG_BLOCK
, &nset
, &oset
);
103 dls
= DLS_NOPERM
| DLS_ABANDON
;
105 /* First of all: we only dotlock when the executing user has the necessary
106 * rights to access the mailbox */
107 if (access(di
.di_file_name
, (argv
[1][0] == 'r' ? R_OK
: R_OK
| W_OK
)))
110 /* We need UID and GID information about the mailbox to lock */
111 if (stat(di
.di_file_name
, di
.di_stb
= &stb
) == -1)
114 dls
= DLS_PRIVFAILED
| DLS_ABANDON
;
116 /* This privsep helper only gets executed when needed, it thus doesn't make
117 * sense to try to continue with initial privileges */
118 if (setuid(geteuid()))
121 dls
= _dotlock_create(&di
);
123 /* Finally: notify our parent about the actual lock state.. */
125 write(STDOUT_FILENO
, &dls
, sizeof dls
);
126 close(STDOUT_FILENO
);
128 /* ..then eventually wait until we shall remove the lock again, which will
129 * be notified via the read returning */
130 if (dls
== DLS_NONE
) {
131 read(STDIN_FILENO
, &dls
, sizeof dls
);
133 unlink(di
.di_lock_name
);
136 sigprocmask(SIG_SETMASK
, &oset
, NULL
);
137 return (dls
== DLS_NONE
? EXIT_OK
: EXIT_ERR
);