diff options
author | Michael I. Bushnell <mib@gnu.org> | 1994-07-21 19:03:46 +0000 |
---|---|---|
committer | Michael I. Bushnell <mib@gnu.org> | 1994-07-21 19:03:46 +0000 |
commit | 1cde8b2682dc438ef0aafa37c4a8ca9863b1e33f (patch) | |
tree | 5eeba5d1f8aa7e8e559805e67b2203f4a1a0fd98 /utils/su.c | |
parent | 3e0f07c7d45f5e6ba6cff0215656db105a5e20af (diff) |
Initial revision
Diffstat (limited to 'utils/su.c')
-rw-r--r-- | utils/su.c | 461 |
1 files changed, 461 insertions, 0 deletions
diff --git a/utils/su.c b/utils/su.c new file mode 100644 index 00000000..3bff70ad --- /dev/null +++ b/utils/su.c @@ -0,0 +1,461 @@ +/* `su' for GNU Hurd. + Copyright (C) 1994 Free Software Foundation + Written by Roland McGrath. + +This file is part of the GNU Hurd. + +The GNU Hurd is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +The GNU Hurd is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with the GNU Hurd; see the file COPYING. If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. + + Unlike Unix su, this program does not spawn a subshell. Instead, it + adds or removes (with the -r flag) auth rights to all the processes in + the current login. Note that the addition and removal of rights to a + particular process is voluntary; the process doesn't get them unless + it's listening for the right rpc (which the C library normally does), + and it doesn't have to release any of the rights it has when requested + (though the C library does this automatically in most programs). + + The -r flag allows you to easily be authorized or not authorized for any + number of users (uids and gid sets). This program is not intelligent, + however. The -r flag always removes whatever gids the specified user + has. If some other user you have obtained authorization for has some of + the same gids, it will remove them. The C library could be intelligent + enough to look up the groups of all the uids it has, and make sure it + has at least those. */ + +#include <stdlib.h> +#include <hurd.h> +#include <pwd.h> +#include <grp.h> +#include <termios.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <getopt.h> +#include <hurd/msg.h> +#include <assert.h> + +process_t proc; /* Our proc server port. */ + +/* Command line flags. */ +int remove_ids = 0; /* -r: Remove ids instead of adding them. */ + +const struct option longopts[] = + { + { "remove", 0, 0, 'r' }, + { "uid", 0, 0, 'u' }, + { "gid", 0, 0, 'g' }, + { "loginuser", 0, 0, 'l' }, + { "pid", 0, 0, 'p' }, + { "pgrp", 0, 0, 'G' }, + { "loginid", 0, 0, 'i' }, + { 0, } + }; + +/* These functions return a malloc'd array of ids that can be passed to + make_auth_handle or used in del_auth messages. They check authorization + and return NULL on failure. */ +unsigned int *get_uid (const char *user); /* From a named user or UID. */ +unsigned int *get_gid (const char *user); /* From a named group or GID. */ +unsigned int *get_user (const char *user); /* All ids for a named user. */ + +int apply_ids (unsigned int *ids, pid_t, int); +int apply_ids_to_pids (unsigned int *ids, unsigned int, pid_t *, int); +int apply_ids_to_pgrp (unsigned int *ids, pid_t); +int apply_ids_to_loginid (unsigned int *ids, int); + +int check_password (const char *name, const char *password); + + +int +main (int argc, char **argv) +{ + int c; + int status; + enum { user, group, loginuser, loginid, pid, pgrp } mode = loginuser; + unsigned int *idsets[argc]; + unsigned int ididx = 0, loginidx = 0, pididx = 0, pgrpidx = 0; + int loginids[argc]; + pid_t pids[argc], pgrps[argc]; + unsigned int i, j; + + proc = getproc (); /* Used often. */ + + while ((c = getopt_long (argc, argv, "--rgulpiG", longopts, NULL)) != EOF) + switch (c) + { + case 'r': + remove_ids = 1; + break; + + case 'u': + mode = user; + break; + case 'g': + mode = group; + break; + case 'l': + mode = loginuser; + break; + case 'p': + mode = pid; + break; + case 'i': + mode = loginid; + break; + case 'G': + mode = pgrp; + break; + + case '\0': /* Non-option argument. */ + switch (mode) + { + case user: + idsets[ididx++] = get_uid (optarg); + break; + case group: + idsets[ididx++] = get_gid (optarg); + break; + case loginuser: + idsets[ididx++] = get_user (optarg); + break; + + case pid: + pids[pididx++] = atoi (optarg); + break; + case loginid: + loginids[loginidx++] = atoi (optarg); + break; + case pgrp: + pgrps[pgrpidx++] = atoi (optarg); + break; + } + break; + + case '?': + default: + fprintf (stderr, "Usage: %s [-r|--remove]\n\ + [-u|--uid UID...] [-g|--gid GID...] [-l|--loginuser USER...]\n\ + [-p|--pid PID...] [-i|--loginid ID...] [-G|--pgrp PGRP...]\n", + program_invocation_short_name); + exit (1); + } + + if (ididx == 0) + /* No ids specified; default is "su root". */ + idsets[ididx++] = get_user ("root"); + + if (pididx == 0 && loginidx == 0 && pgrpidx == 0) + /* No processes specified; default is current login collection. */ + if (errno = proc_getloginid (proc, getpid (), &loginids[loginidx++])) + { + perror ("proc_getloginid"); + return 1; + } + + status = 0; + + for (j = 0; j < ididx; ++j) + status |= apply_ids_to_pids (idsets[j], pididx, pids, 0); + + for (i = 0; i < loginidx; ++i) + { + for (j = 0; j < loginidx; ++j) + status |= apply_ids_to_loginid (idsets[j], loginids[i]); + } + + for (i = 0; i < pgrpidx; ++i) + { + for (j = 0; j < pgrpidx; ++j) + status |= apply_ids_to_loginid (idsets[j], pgrps[i]); + } + + + return status; +} + +/* Turn USER into a list of ids, giving USER's primary uid and gid from the + passwd file and all the groups that USER appears in in the group file. */ + +unsigned int * +get_user (const char *user) +{ + struct passwd *p; + unsigned int ngids, *ids; + + p = getpwnam (user); + if (p == NULL) + { + fprintf (stderr, "%s: User `%s' not found\n", + program_invocation_short_name, user); + return NULL; + } + + if (! check_password (user, p->pw_passwd)) + return NULL; + + /* We don't need to change our own gids, but it doesn't really hurt, + and initgroups does all the work for us. */ + if (initgroups (user, p->pw_gid) < 0) + { + perror ("initgroups"); + return NULL; + } + ngids = getgroups (0, NULL); + if ((int) ngids < 0) + { + getgroups_lost: + perror ("getgroups"); + return NULL; + } + assert (sizeof (*ids) == sizeof (gid_t)); + ids = malloc ((3 + ngids) * sizeof (*ids)); + ids[0] = 1; + ids[1] = p->pw_uid; + ids[2] = getgroups (ngids, &ids[3]); + if ((int) ids[2] < 0) + goto getgroups_lost; + + return ids; +} + +/* Return an id list containing just the uid of USER. */ + +unsigned int * +get_uid (const char *user) +{ + struct passwd *p; + unsigned int *ids; + uid_t uid; + char *uend; + + uid = strtoul (user, &uend, 10); + if (uend && *uend == '\0') + { + if (remove_ids || getuid () == 0) + /* No need to verify. */ + p = NULL; + else + { + p = getpwuid (uid); + if (p == NULL) + { + fprintf (stderr, "%s: UID %u not found\n", + program_invocation_short_name, uid); + return NULL; + } + } + } + else + { + p = getpwnam (user); + if (p == NULL) + { + fprintf (stderr, "%s: User `%s' not found\n", + program_invocation_short_name, user); + return NULL; + } + } + + if (p != NULL && ! check_password (user, p->pw_passwd)) + return NULL; + + ids = malloc (3 * sizeof (*ids)); + ids[0] = 1; + ids[1] = p->pw_uid; + ids[2] = 0; + + return ids; +} + +/* Return an id list containing just the gid of GROUP. */ + +unsigned int * +get_gid (const char *group) +{ + struct group *g; + unsigned int *ids; + gid_t gid; + char *gend; + + gid = strtoul (group, &gend, 10); + if (gend && *gend == '\0') + { + if (remove_ids || getuid () == 0) + /* No need to verify. */ + g = NULL; + else + { + g = getgrgid (gid); + if (g == NULL) + { + fprintf (stderr, "%s: GID %u not found\n", + program_invocation_short_name, gid); + return NULL; + } + } + } + else + { + g = getgrnam (group); + if (g == NULL) + { + fprintf (stderr, "%s: Group `%s' not found\n", + program_invocation_short_name, group); + return NULL; + } + } + + if (g != NULL && ! check_password (group, g->gr_passwd)) + return NULL; + + ids = malloc (3 * sizeof (*ids)); + ids[0] = 0; + ids[1] = 1; + ids[2] = g->gr_gid; + + return ids; +} + +/* Add or delete (under -r) the ids indicated by IDS to/from PID. If + IGNORE_BAD_PID is nonzero, return success if PID does not exist. + Returns zero if successful, nonzero on error (after printing message). */ + +int +apply_ids (unsigned int *ids, pid_t pid, int ignore_bad_pid) +{ + error_t err; + auth_t auth; + + if (! ids) + return 0; + + if (! remove_ids) + if (errno = auth_makeauth (getauth (), NULL, MACH_MSG_TYPE_COPY_SEND, 0, + &ids[1], ids[0], NULL, 0, + &ids[1 + ids[0] + 1], ids[1 + ids[0]], NULL, 0, + &auth)) + { + perror ("auth_makeauth"); + return 1; + } + + err = HURD_MSGPORT_RPC (proc_getmsgport (proc, pid, &msgport), + proc_pid2task (proc, pid, &refport), + remove_ids ? + del_auth (msgport, refport, + &ids[1], ids[0], + &ids[1 + ids[0] + 1], + ids[1 + ids[0]]) : + add_auth (msgport, auth)); + if (err && + (!ignore_bad_pid || (err != ESRCH && err != MIG_SERVER_DIED))) + { + fprintf (stderr, "%s: error in %s_auth from PID %d: %s\n", + program_invocation_short_name, + remove_ids ? "del" : "add", pid, strerror (err)); + return 1; + } + else + return 0; +} + +int +apply_ids_to_pids (unsigned int *ids, unsigned int npids, pid_t pids[], + int ignore_bad_pid) +{ + int status = 0; + unsigned int i; + + for (i = 0; i < npids; ++i) + status |= apply_ids (ids, pids[i], ignore_bad_pid); + + return status; +} + +int +apply_ids_to_loginid (unsigned int *ids, int loginid) +{ + unsigned int npids = 20; + pid_t pidbuf[20], *pids = pidbuf; + int status; + error_t err; + + if (err = proc_getloginpids (proc, loginid, &pids, &npids)) + { + fprintf (stderr, "%s: proc_getloginpids failed for loginid %d: %s\n", + program_invocation_short_name, loginid, strerror (err)); + return 1; + } + + status = apply_ids_to_pids (ids, npids, pids, 1); + + if (pids != pidbuf) + vm_deallocate (mach_task_self (), + (vm_address_t) pids, npids * sizeof (pid_t)); + + return status; +} + +int +apply_ids_to_pgrp (unsigned int *ids, pid_t pgrp) +{ + unsigned int npids = 20; + pid_t pidbuf[20], *pids = pidbuf; + int status; + error_t err; + + if (err = proc_getpgrppids (proc, pgrp, &pids, &npids)) + { + fprintf (stderr, "%s: proc_getpgrppids failed for pgrp %d: %s\n", + program_invocation_short_name, pgrp, strerror (err)); + return 1; + } + + status = apply_ids_to_pids (ids, npids, pids, 1); + + if (pids != pidbuf) + vm_deallocate (mach_task_self (), + (vm_address_t) pids, npids * sizeof (pid_t)); + + return status; +} + +/* Return 1 if the user gives the correct password matching the encrypted + string PASSWORD, 0 if not. Return 1 without asking for a password if + run by uid 0 or if PASSWORD is an empty password, and always under -r. + Always prints a message before returning 0. */ + +int +check_password (const char *name, const char *password) +{ + extern char *crypt (const char salt[2], const char *string); + char *unencrypted, *encrypted; + static char *prompt = NULL; + + if (remove_ids || getuid () == 0 || password == NULL || password[0] == '\0') + return 1; + + asprintf (&prompt, "%s's Password:", name); + unencrypted = getpass (prompt); + encrypted = crypt (unencrypted, password); + memset (unencrypted, 0, strlen (unencrypted)); /* Paranoia may destroya. */ + + if (!strcmp (encrypted, password)) + return 1; + + fprintf (stderr, "%s: Access denied for `%s'\n", + program_invocation_short_name, name); + return 0; +} |