Initial revision
[become] / src / become.c
CommitLineData
c4f2d992 1/* -*-c-*-
2 *
3 * $Id: become.c,v 1.1 1997/07/21 13:47:54 mdw Exp $
4 *
5 * Main code for `become'
6 *
7 * (c) 1997 EBI
8 */
9
10/*----- Licencing notice --------------------------------------------------*
11 *
12 * This file is part of `become'
13 *
14 * `Become' is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
18 *
19 * `Become' is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with `become'; if not, write to the Free Software
26 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 */
28
29/*----- Revision history --------------------------------------------------*
30 *
31 * $Log: become.c,v $
32 * Revision 1.1 1997/07/21 13:47:54 mdw
33 * Initial revision
34 *
35 */
36
37/*----- Header files ------------------------------------------------------*/
38
39/* --- ANSI headers --- */
40
41#include <ctype.h>
42#include <errno.h>
43#include <stdio.h>
44#include <stdlib.h>
45#include <string.h>
46
47/* --- Unix headers --- */
48
49#include <sys/types.h>
50#include <sys/stat.h>
51#include <sys/socket.h>
52#include <sys/utsname.h>
53
54#include <netinet/in.h>
55
56#include <arpa/inet.h>
57
58#include <netdb.h>
59#include <pwd.h>
60#include <syslog.h>
61#include <unistd.h>
62
63/* --- Local headers --- */
64
65#include "become.h"
66#include "config.h"
67#include "check.h"
68#include "daemon.h"
69#include "lexer.h"
70#include "mdwopt.h"
71#include "name.h"
72#include "parser.h"
73#include "rule.h"
74#include "utils.h"
75
76/*----- Main code ---------------------------------------------------------*/
77
78/* --- @bc__write@ --- *
79 *
80 * Arguments: @FILE *fp@ = pointer to a stream to write on
81 * @const char *p@ = pointer to a string
82 *
83 * Returns: ---
84 *
85 * Use: Writes the string to the stream, substituting the program
86 * name (as returned by @quis@) for each occurrence of the
87 * character `$'.
88 */
89
90static void bc__write(FILE *fp, const char *p)
91{
92 const char *n = quis();
93 size_t l;
94 size_t nl = strlen(n);
95
96 /* --- Try to be a little efficient --- *
97 *
98 * Gather up non-`$' characters using @strcpn@ and spew them out really
99 * quickly.
100 */
101
102 for (;;) {
103 l = strcspn(p, "$");
104 if (l)
105 fwrite(p, l, 1, fp);
106 p += l;
107 if (!*p)
108 break;
109 fwrite(n, nl, 1, fp);
110 p++;
111 }
112}
113
114/* --- @bc__banner@ --- *
115 *
116 * Arguments: @FILE *fp@ = stream to write on
117 *
118 * Returns: ---
119 *
120 * Use: Writes a banner containing copyright information.
121 */
122
123static void bc__banner(FILE *fp)
124{
125 bc__write(fp, "$ version " VERSION "\n");
126}
127
128/* --- @bc__usage@ --- *
129 *
130 * Arguments: @FILE *fp@ = stream to write on
131 *
132 * Returns: ---
133 *
134 * Use: Writes a terse reminder of command line syntax.
135 */
136
137static void bc__usage(FILE *fp)
138{
139 bc__write(fp,
140 "Usage: \n"
141 " $ -c <shell-command> <user>\n"
142 " $ <user> [<command> [<arguments>]...]\n"
143 " $ -d [-p <port>] [-f <config-file>]\n");
144}
145
146/* --- @bc__help@ --- *
147 *
148 * Arguments: @FILE *fp@ = stream to write on
149 *
150 * Returns: ---
151 *
152 * Use: Displays a help message for this excellent piece of software.
153 */
154
155static void bc__help(FILE *fp)
156{
157 bc__banner(fp);
158 putc('\n', fp);
159 bc__usage(fp);
160 putc('\n', fp);
161 bc__write(fp,
162"The `$' program allows you to run a process as another user.\n"
163"If a command name is given, this is the process executed. If the `-c'\n"
164"option is used, the process is assumed to be `/bin/sh'. If no command is\n"
165"given, your default login shell is used.\n"
166"\n"
167"Your user id, the user id you wish to become, the name of the process\n"
168"you wish to run, and the identity of the current host are looked up to\n"
169"ensure that you have permission to do this.\n"
170"\n"
171"Note that logs are kept of all uses of this program.\n"
172"\n"
173"Options available are:\n"
174"\n"
175"-h, --help Display this help text\n"
176"-v, --version Display the version number of this copy of $\n"
177"-c, --command=CMD Run the (Bourne) shell command CMD\n"
178"-d, --daemon Start up a daemon, to accept requests from clients\n"
179"-p, --port=PORT In daemon mode, listen on PORT\n"
180"-f, --config-file=FILE In daemon mode, read config from FILE\n"
181"--yacc-debug Dump lots of parser diagnostics (boring)\n");
182}
183
184/* --- @main@ --- *
185 *
186 * Arguments: @int argc@ = number of command line arguments
187 * @char *argv[]@ = pointer to the various arguments
188 *
189 * Returns: Zero if successful.
190 *
191 * Use: Allows a user to change UID.
192 */
193
194int main(int argc, char *argv[])
195{
196 char *cmd = 0;
197 static char *shell[] = { "/bin/sh", "-c", 0, 0 };
198 char **todo;
199 request rq;
200 char buff[CMDLEN_MAX];
201 char *conffile = file_RULES;
202 int port = -1;
203
204 enum {
205 f_daemon = 1,
206 f_duff = 2
207 };
208
209 unsigned flags = 0;
210
211 /* --- Set up the program name --- */
212
213 ego(argv[0]);
214
215 /* --- Parse some command line arguments --- */
216
217 for (;;) {
218 int i;
219 struct option opts[] = {
220 { "help", 0, 0, 'h' },
221 { "version", 0, 0, 'v' },
222 { "command", gFlag_argReq, 0, 'c' },
223 { "daemon", 0, 0, 'd' },
224 { "port", gFlag_argReq, 0, 'p' },
225 { "config-file", gFlag_argReq, 0, 'f' },
226 { "yacc-debug", 0, 0, 'Y' },
227 { 0, 0, 0, 0 }
228 };
229
230 i = mdwopt(argc, argv, "hvc:p:df:", opts, 0, 0, 0);
231 if (i < 0)
232 break;
233
234 switch (i) {
235 case 'h':
236 bc__help(stdout);
237 exit(0);
238 break;
239 case 'v':
240 bc__banner(stdout);
241 exit(0);
242 break;
243 case 'c':
244 cmd = optarg;
245 break;
246 case 'p':
247 port = atoi(optarg);
248 break;
249 case 'd':
250 flags |= f_daemon;
251 break;
252 case 'f':
253 conffile = optarg;
254 break;
255 case 'Y':
256 if (getuid() == geteuid())
257 yydebug = 1;
258 else
259 moan("won't set debugging mode when running setuid");
260 break;
261 case '?':
262 flags |= f_duff;
263 break;
264 }
265 }
266 if (flags & f_duff) {
267 bc__usage(stderr);
268 exit(1);
269 }
270
271 /* --- Switch to daemon mode if requested --- */
272
273 if (flags & f_daemon) {
274 daemon_init(conffile, port);
275 exit(0);
276 }
277
278 /* --- Open a syslog --- */
279
280 openlog(quis(), 0, LOG_AUTH);
281
282 /* --- Pick out the uid --- */
283
284 {
285 const char *u;
286 struct passwd *pw;
287 if (optind >= argc) {
288 bc__usage(stderr);
289 exit(1);
290 }
291 u = argv[optind++];
292 pw = getpwnam(u);
293 if (!pw && isdigit(u[0]))
294 pw = getpwuid(atoi(u));
295 if (!pw)
296 die("unknown user `%s'", u);
297 rq.to = pw->pw_uid;
298 }
299
300 /* --- Fill in the easy bits of the request --- */
301
302 rq.from = getuid();
303
304 /* --- Find the local host address --- */
305
306 {
307 struct utsname u;
308 struct hostent *he;
309 uname(&u);
310 if ((he = gethostbyname(u.nodename)) == 0)
311 die("who am I? (can't resolve `%s')", u.nodename);
312 memcpy(&rq.host, he->h_addr, sizeof(struct in_addr));
313 }
314
315 /* --- Figure out what command to use --- */
316
317 if (cmd) {
318 shell[2] = cmd;
319 todo = shell;
320 } else if (optind < argc) {
321 todo = argv + optind;
322 } else {
323 struct passwd *pw = getpwuid(rq.from);
324 if (!pw)
325 die("who are you? (can't find uid %li in database)", (long)rq.from);
326 shell[0] = pw->pw_shell;
327 shell[1] = 0;
328 todo = shell;
329 }
330
331 /* --- If necessary, resolve the path to the command --- */
332
333 if (!strchr(todo[0], '/')) {
334 char *path;
335 char *p;
336 struct stat st;
337 size_t sz;
338
339 if ((p = getenv("PATH")) == 0)
340 p = "/bin:/usr/bin";
341 sz = strlen(p) + 1;
342 memcpy(path = xmalloc(sz), p, sz);
343
344 for (p = strtok(path, ":"); (p = strtok(0, ":")) != 0; ) {
345
346 /* --- SECURITY: check length of string before copying --- */
347
348 if (strlen(p) + strlen(todo[0]) + 2 > sizeof(buff))
349 continue;
350
351 /* --- Now build the pathname and check it --- */
352
353 sprintf(buff, "%s/%s", p, todo[0]);
354 if (stat(buff, &st) == 0 && /* Check it exists */
355 st.st_mode & 0111 && /* Check it's executable */
356 (st.st_mode & S_IFMT) == S_IFREG) /* Check it's a file */
357 break;
358 }
359
360 if (!p)
361 die("couldn't find `%s' in path", todo[0]);
362 todo[0] = buff;
363 free(path);
364 }
365
366 /* --- Canonicalise the path string, if necessary --- */
367
368 {
369 char b[CMDLEN_MAX];
370 char *p;
371 const char *q;
372
373 /* --- Insert current directory name if path not absolute --- */
374
375 p = b;
376 q = todo[0];
377 if (*q != '/') {
378 if (!getcwd(b, sizeof(b)))
379 die("couldn't read current directory: %s", strerror(errno));
380 p += strlen(p);
381 *p++ = '/';
382 }
383
384 /* --- Now copy over characters from the path string --- */
385
386 while (*q) {
387
388 /* --- SECURITY: check for buffer overflows here --- *
389 *
390 * I write at most one byte per iteration so this is OK. Remember to
391 * allow one for the null byte.
392 */
393
394 if (p >= b + sizeof(b) - 1)
395 die("buffer overflow -- bad things happened");
396
397 /* --- Reduce multiple slashes to just one --- */
398
399 if (*q == '/') {
400 while (*q == '/')
401 q++;
402 *p++ = '/';
403 }
404
405 /* --- Handle dots in filenames --- *
406 *
407 * @p[-1]@ is valid here, because if @*q@ is not a `/' then either
408 * we've just stuck the current directory on the end of the buffer,
409 * or we've just put something else on the end.
410 */
411
412 else if (*q == '.' && p[-1] == '/') {
413
414 /* --- A simple `./' just gets removed --- */
415
416 if (q[1] == 0 || q[1] == '/') {
417 q++;
418 p--;
419 continue;
420 }
421
422 /* --- A `../' needs to be peeled back to the previous `/' --- */
423
424 if (q[1] == '.' && (q[2] == 0 || q[2] == '/')) {
425 q += 2;
426 p--;
427 while (p > b && p[-1] != '/')
428 p--;
429 if (p > b)
430 p--;
431 continue;
432 }
433 } else
434 *p++ = *q++;
435 }
436
437 *p++ = 0;
438 strcpy(rq.cmd, b);
439 }
440
441 /* --- Run the check --- */
442
443 {
444 int a = check(&rq);
445 char from[16], to[16];
446 struct passwd *pw;
447
448 if ((pw = getpwuid(rq.from)) != 0)
449 sprintf(from, "%.15s", pw->pw_name);
450 else
451 sprintf(from, "user %lu", (unsigned long)rq.from);
452
453 if ((pw = getpwuid(rq.to)) != 0)
454 sprintf(to, "%.15s", pw->pw_name);
455 else
456 sprintf(to, "user %lu", (unsigned long)rq.to);
457
458 syslog(LOG_INFO,
459 "permission %s for %s to become %s to run `%s'",
460 a ? "granted" : "denied", from, to, rq.cmd);
461
462 if (!a)
463 die("permission denied");
464 }
465
466 /* --- Now do the job --- */
467
468#ifdef TEST_RIG
469 printf("ok\n");
470 return (0);
471#else
472 if (setuid(rq.to) == -1 || seteuid(rq.to) == -1)
473 die("couldn't set uid: %s", strerror(errno));
474 execv(rq.cmd, todo);
475 die("couldn't exec `%s': %s", rq.cmd, strerror(errno));
476 return (127);
477#endif
478}
479
480/*----- That's all, folks -------------------------------------------------*/