3 * $Id: become.c,v 1.1 1997/07/21 13:47:54 mdw Exp $
5 * Main code for `become'
10 /*----- Licencing notice --------------------------------------------------*
12 * This file is part of `become'
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.
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.
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.
29 /*----- Revision history --------------------------------------------------*
32 * Revision 1.1 1997/07/21 13:47:54 mdw
37 /*----- Header files ------------------------------------------------------*/
39 /* --- ANSI headers --- */
47 /* --- Unix headers --- */
49 #include <sys/types.h>
51 #include <sys/socket.h>
52 #include <sys/utsname.h>
54 #include <netinet/in.h>
56 #include <arpa/inet.h>
63 /* --- Local headers --- */
76 /*----- Main code ---------------------------------------------------------*/
78 /* --- @bc__write@ --- *
80 * Arguments: @FILE *fp@ = pointer to a stream to write on
81 * @const char *p@ = pointer to a string
85 * Use: Writes the string to the stream, substituting the program
86 * name (as returned by @quis@) for each occurrence of the
90 static void bc__write(FILE *fp
, const char *p
)
92 const char *n
= quis();
94 size_t nl
= strlen(n
);
96 /* --- Try to be a little efficient --- *
98 * Gather up non-`$' characters using @strcpn@ and spew them out really
109 fwrite(n
, nl
, 1, fp
);
114 /* --- @bc__banner@ --- *
116 * Arguments: @FILE *fp@ = stream to write on
120 * Use: Writes a banner containing copyright information.
123 static void bc__banner(FILE *fp
)
125 bc__write(fp
, "$ version " VERSION
"\n");
128 /* --- @bc__usage@ --- *
130 * Arguments: @FILE *fp@ = stream to write on
134 * Use: Writes a terse reminder of command line syntax.
137 static void bc__usage(FILE *fp
)
141 " $ -c <shell-command> <user>\n"
142 " $ <user> [<command> [<arguments>]...]\n"
143 " $ -d [-p <port>] [-f <config-file>]\n");
146 /* --- @bc__help@ --- *
148 * Arguments: @FILE *fp@ = stream to write on
152 * Use: Displays a help message for this excellent piece of software.
155 static void bc__help(FILE *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"
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"
171 "Note that logs are kept of all uses of this program.\n"
173 "Options available are:\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");
186 * Arguments: @int argc@ = number of command line arguments
187 * @char *argv[]@ = pointer to the various arguments
189 * Returns: Zero if successful.
191 * Use: Allows a user to change UID.
194 int main(int argc
, char *argv
[])
197 static char *shell
[] = { "/bin/sh", "-c", 0, 0 };
200 char buff
[CMDLEN_MAX
];
201 char *conffile
= file_RULES
;
211 /* --- Set up the program name --- */
215 /* --- Parse some command line arguments --- */
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' },
230 i
= mdwopt(argc
, argv
, "hvc:p:df:", opts
, 0, 0, 0);
256 if (getuid() == geteuid())
259 moan("won't set debugging mode when running setuid");
266 if (flags
& f_duff
) {
271 /* --- Switch to daemon mode if requested --- */
273 if (flags
& f_daemon
) {
274 daemon_init(conffile
, port
);
278 /* --- Open a syslog --- */
280 openlog(quis(), 0, LOG_AUTH
);
282 /* --- Pick out the uid --- */
287 if (optind
>= argc
) {
293 if (!pw
&& isdigit(u
[0]))
294 pw
= getpwuid(atoi(u
));
296 die("unknown user `%s'", u
);
300 /* --- Fill in the easy bits of the request --- */
304 /* --- Find the local host address --- */
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
));
315 /* --- Figure out what command to use --- */
320 } else if (optind
< argc
) {
321 todo
= argv
+ optind
;
323 struct passwd
*pw
= getpwuid(rq
.from
);
325 die("who are you? (can't find uid %li in database)", (long)rq
.from
);
326 shell
[0] = pw
->pw_shell
;
331 /* --- If necessary, resolve the path to the command --- */
333 if (!strchr(todo
[0], '/')) {
339 if ((p
= getenv("PATH")) == 0)
342 memcpy(path
= xmalloc(sz
), p
, sz
);
344 for (p
= strtok(path
, ":"); (p
= strtok(0, ":")) != 0; ) {
346 /* --- SECURITY: check length of string before copying --- */
348 if (strlen(p
) + strlen(todo
[0]) + 2 > sizeof(buff
))
351 /* --- Now build the pathname and check it --- */
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 */
361 die("couldn't find `%s' in path", todo
[0]);
366 /* --- Canonicalise the path string, if necessary --- */
373 /* --- Insert current directory name if path not absolute --- */
378 if (!getcwd(b
, sizeof(b
)))
379 die("couldn't read current directory: %s", strerror(errno
));
384 /* --- Now copy over characters from the path string --- */
388 /* --- SECURITY: check for buffer overflows here --- *
390 * I write at most one byte per iteration so this is OK. Remember to
391 * allow one for the null byte.
394 if (p
>= b
+ sizeof(b
) - 1)
395 die("buffer overflow -- bad things happened");
397 /* --- Reduce multiple slashes to just one --- */
405 /* --- Handle dots in filenames --- *
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.
412 else if (*q
== '.' && p
[-1] == '/') {
414 /* --- A simple `./' just gets removed --- */
416 if (q
[1] == 0 || q
[1] == '/') {
422 /* --- A `../' needs to be peeled back to the previous `/' --- */
424 if (q
[1] == '.' && (q
[2] == 0 || q
[2] == '/')) {
427 while (p
> b
&& p
[-1] != '/')
441 /* --- Run the check --- */
445 char from
[16], to
[16];
448 if ((pw
= getpwuid(rq
.from
)) != 0)
449 sprintf(from
, "%.15s", pw
->pw_name
);
451 sprintf(from
, "user %lu", (unsigned long)rq
.from
);
453 if ((pw
= getpwuid(rq
.to
)) != 0)
454 sprintf(to
, "%.15s", pw
->pw_name
);
456 sprintf(to
, "user %lu", (unsigned long)rq
.to
);
459 "permission %s for %s to become %s to run `%s'",
460 a ?
"granted" : "denied", from
, to
, rq
.cmd
);
463 die("permission denied");
466 /* --- Now do the job --- */
472 if (setuid(rq
.to
) == -1 || seteuid(rq
.to
) == -1)
473 die("couldn't set uid: %s", strerror(errno
));
475 die("couldn't exec `%s': %s", rq
.cmd
, strerror(errno
));
480 /*----- That's all, folks -------------------------------------------------*/