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 | |
90 | static 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 | |
123 | static 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 | |
137 | static 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 | |
155 | static 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 | |
194 | int 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 -------------------------------------------------*/ |