static const char rcsid[] = "$Id: checks.c,v 1.181 2006/12/13 21:16:42 will Exp $"; /* The code should compile with either ANSI C or K&R compilers. */ /* * Copyright (c) 1993 by California Institute of Technology. * Written by William Deich. Not derived from licensed software. * You may distribute under the terms of either the GNU General Public * License or the Artistic License, as specified in the README file. */ #include "super.h" #include "version.h" #ifdef HAVE_INNETGR #define netgrp_u_compare(pattern, user) innetgr(pattern, NULL, user, NULL) #define netgrp_h_compare(pattern, host) innetgr(pattern, host, NULL, NULL) #else #define netgrp_u_compare(p, u) 0 #define netgrp_h_compare(p, h) 0 #endif int get_setting P__ (()); #ifdef _HPUX_SOURCE #ifdef HAVE_ISCOMSEC char *bigcrypt(); #else /* If we don't have iscomsec() on this HP system, fake it -- otherwise * we'll have to #ifdef the use of iscomsec(), and it's cleaner code * if we don't. */ int iscomsec() { return 0; } #endif #endif /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Check that an environment variable only includes allowed characters */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Returns 0 if pat matched; -1 otherwise. */ int checkenv(name, value, pat) char *name; /* variable name to check (e.g. "TERM") */ char *value; /* contents of variable (e.g. "vt100") */ char *pat; /* pattern that value must match */ { int len; if (!value) return -1; if (debug) (void) fprintf(stderr, "\tcheckenv args: name=\"%s\"; value=\"%s\"; pat=\"%s\"\n", name, value, pat); /* Environment variables are always checked with s_re_comp/s_re_exec: * the patterns are fixed internally, not supplied by the user. */ if (s_re_comp(pat)) return Error(0, 0, "%t\n\tcheckenv(): couldn't compile pattern `%-.500s'.\n", pat); if (s_re_exec(value) != 1) return Error(0, 0, "checkenv(): $%.100s (=%.100s) doesn't match pattern %-.500s.\n", name, value, pat); /* Limit the value to a reasonable length (MAXENVLEN chars) */ len = strlen(value); if (len > MAXENVLEN) { return Error(0, 0, "Imported environment variables may not \ exceed %d chars; you passed a string of length %d!\n", MAXENVLEN, len); } return 0; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Check the checkvar=name,... list. * If the list isn't empty, use /dev/tty for input. */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Returns 0 if list ok; -1 otherwise. */ int check_var_value() { int l, ntry, match; char buf[500]; char *varname, *truevalue; int ivar; FILE *prompt_fp; char *prompt_dev="/dev/tty"; /* This is implemented as an inefficient operation, but it doesn't * matter because we _assume_ the number of variables to be small * and it's only invoked once. */ if (!localinfo.checkvar || !localinfo.checkvar[0]) return 0; /* no checkvar list */ if (!(prompt_fp = fopen(prompt_dev, "r+"))) { return Error(0, 0, "Can't open %s to test checkvar=name,...\n", prompt_dev); } if (localinfo.checkvar[1]) { /* There must be more than one variable */ fprintf(prompt_fp, "%c%s needs you enter some variables \ before proceeding...\n", toupper(prog[0]), prog+1); } else { fprintf(prompt_fp, "%c%s needs you to enter the %s variable \ before proceeding.\n", toupper(prog[0]), prog+1, localinfo.checkvar[0]); } for (ivar=0; localinfo.checkvar[ivar]; ivar++) { varname = localinfo.checkvar[ivar]; truevalue = get_variable(varname); if (!truevalue) { /* Ouch, checkvar=xxx is used, but xxx isn't defined! */ fclose(prompt_fp); return Error(0, 0, "%t\n\tError in super.tab file: \ `checkvar=%s' is used, but variable %s isn't defined!\n", varname, varname); } for (ntry=0, match=0; ntry < MAXTRY && !match; ntry++) { if (ntry == 0) { fprintf(prompt_fp, "Enter %s ( for reminder): ", varname); } else { fprintf(prompt_fp, "Enter %s (expecting `%s'): ", varname, truevalue); } fflush(prompt_fp); if (!fgets(buf, sizeof(buf), prompt_fp)) { if (feof(prompt_fp)) { fclose(prompt_fp); return Error(0, 0, "can't read %s -- stream was closed!\n", prompt_dev); } else if (ferror(prompt_fp)) { fclose(prompt_fp); return Error(0, 0, "error reading %s\n", prompt_dev); } else { fclose(prompt_fp); return Error(0, 0, "??? feof() and ferror() return 0, \ but I can't read %s\n", prompt_dev); } } if (strlen(buf) == sizeof(buf)-1) { fclose(prompt_fp); return Error(0, 0, "Ridiculously long value <%.300s...> \ returned for variable %s\n", buf, varname); } l = strlen(buf); if (buf[l-1] == '\n') buf[l-1] = '\0'; match = (strcmp(buf, truevalue) == 0); if (!match) { fprintf(prompt_fp, "Variable %s incorrect\n", varname); } } if (!match) { fclose(prompt_fp); return Error(0, 0, "Never got variable %s entered correctly.\n", varname); } } fclose(prompt_fp); /* all variables are ok. */ return 0; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Compare a value to a pattern */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Returns 0 if pat matched; -1 otherwise. */ int check_value(value, pat) char *value; /* value to check */ char *pat; /* pattern that value must match */ { if (!value) return -1; if (debug) (void) fprintf(stderr, "\tcheck_value args: value=\"%s\"; pat=\"%s\"\n", value, pat); if (s_re_comp(pat)) return Error(0, 0, "%t\n\tcheck_value(): couldn't compile pattern `%-.500s'.\n", pat); if (s_re_exec(value) != 1) return Error(0, 0, "check_value(): value doesn't match pattern %-.500s.\n", pat); return 0; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Option checking -- ensure that options to super are reasonable strings. */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Returns 0 if reasonable; -1 otherwise. */ int checkarg(str) char *str; { int len; static int optlen_tot = 0; /* running length of all options to super */ /* Limit each option to a reasonable length (MAXOPTLEN chars) */ len = strlen(str); if (len > MAXOPTLEN) { return Error(0, 0, "Command-line options may not \ exceed %d chars; you passed a string of length %d!\n", MAXOPTLEN, len); } /* Limit the combined option to length MAXOPTLEN_TOT chars */ optlen_tot += len; if (optlen_tot > MAXOPTLEN_TOT) { return Error(0, 0, "The total length of command-line options \ may not exceed %d chars!\n", MAXOPTLEN_TOT); } /* Ensure the option pattern matches OPT_PATTERN */ if (check_value(str, OPT_PATTERN) != 0) return -1; return 0; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Look up owner of a file, return uid and gid of owner */ /* Return 0 on success, -1 & print message on failure */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ int get_owner(file, uid_p, gid_p) char *file; uid_t *uid_p; gid_t *gid_p; { /* Return 0 if file ownership ok; -1 if not ok */ struct stat st; if (!file || *file == '\0') return Error(0, 0, "get_owner(): passed null ptr or empty string\n"); if (stat(file, &st) == -1) return Error(1, 0, "stat() failed on file `%s': ", file); *uid_p = st.st_uid; *gid_p = st.st_gid; return 0; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Check ownership of the file */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ int check_owner() { /* Return 0 if file ownership ok; -1 if not ok */ struct passwd *owner_pw; if (*localinfo.owner == '\0') return 0; /* no checks required */ /* Convert desired-owner string to a uid */ owner_pw = getpwentry(1, localinfo.owner); if (!owner_pw) return -1; if (localinfo.file_uid != owner_pw->pw_uid) return Error(0, 0, "Actual owner of `%s' is uid %d, but superfile \ requires owner to be %d (%s).\n", localinfo.progs.cmd_file[localinfo.progs.match].File, localinfo.file_uid, owner_pw->pw_uid, owner_pw->pw_name); return 0; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Gets an entry from the password file. Optionally accepts special bracketed * name (e.g. or ). Accepts names or text uid's. * Returns ptr to password entry. The password entry may be that returned * by getpwnam(), or it may be one already stored in a super-owned struct; * you can't make any assumptions about it -- therefore don't modify it. * * The returned pointer points to an area that may be overwritten by later * calls to the getpwxxx() routines; therefore if the caller wants to save * the data, the data must be copied. * * On error, print message and return NULL. */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ struct passwd * getpwentry(allow_brackets, username) int allow_brackets; /* allow special names like */ char *username; /* name to translate */ { struct passwd *pw; int l = strlen(username); if (allow_brackets && username[0] == '<' && username[l-1] == '>') { /* Translate special name */ if (strcmp(username, "") == 0) { if (localinfo.file_uid != UID_NOTSET) { pw = getpwuid(localinfo.file_uid); } else { Error(0, 0, "%t: getpwentry() Internal Error!\n\tFile owner not yet known!\n"); return NULL; } } else if (strcmp(username, "") == 0) { pw = &userinfo.caller; } else { Error(0, 0, "%t\n\t\tUnknown special name %s\n", username); return NULL; } } else { /* Regular name or number */ pw = getpwnam(username); if (!pw) { char c; int i, numeric; numeric = (sscanf(username, "%d%c", &i, &c) == 1); if (numeric) pw = getpwuid(i); } } if (!pw) { Error(0, 0, "%t\n\tNo such user or uid as `%s' in password file.\n", username); return NULL; } return pw; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Gets a group entry. * Accepts text gid's or names. * Returns ptr to group entry. * * The returned pointer points to an area that may be overwritten by later * calls to the getgrxxx() routines; therefore if the caller wants to save * the data, the data must be copied. * * On error, print message and return NULL. */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ struct group * getgrentry(name) char *name; /* name to translate */ { struct group *gr; char c; int i, numeric; gr = getgrnam(name); if (!gr) { numeric = (sscanf(name, "%d%c", &i, &c) == 1); if (numeric) gr = getgrgid(i); } if (!gr) { Error(0, 0, "%t\n\tNo such group or gid as `%s'.\n", name); return NULL; } return gr; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Set supplementary groups according to the specified args */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef HAVE_GETGROUPS int set_suppl_groups() { return 0; } #else int set_suppl_groups() { GETGROUPS_T *addgroups_p, *groups_p, groups[NGROUPS_MAX]; GETGROUPS_T gotten_groups[NGROUPS_MAX]; int naddgroups, ngroups; int i; /* Set the supplementary groups */ if (geteuid() != 0) { /* Can't set supplementary groups if we've changed to some other uid */ return 0; } if (*localinfo.u_g) { /* Default is to take from that user. (Don't worry if there is no * such user; it'll get noticed later.) Conflicts with local * option groups=xxx, which is disallowed. */ if (localinfo.ngroups != GROUPS_NOTSET && !localinfo.groups_added) return Error(0, 0, "%t\n\t\tu+g=xxx conflicts with groups=yyy \ and may not be used in the same entry\n"); initgroups(localinfo.user, userinfo.new_gid); /* Check for local or global addgroups */ if (localinfo.ngroups != GROUPS_NOTSET) { addgroups_p = localinfo.groups; naddgroups = localinfo.ngroups; } else if (globalinfo.ngroups != GROUPS_NOTSET && globalinfo.groups_added) { addgroups_p = globalinfo.groups; naddgroups = globalinfo.ngroups; } else { addgroups_p = NULL; naddgroups = 0; } ngroups = Getgroups(NGROUPS_MAX, gotten_groups); if (ngroups == -1) return Error(1, 0, "%t Getgroups() failed: "); if (ngroups + naddgroups > NGROUPS_MAX) return Error(1, 0, "%t\n\t\taddgroups=xxx adds too many groups."); for (i = 0; i < ngroups; i++) groups[i] = gotten_groups[i]; for (groups_p = &groups[ngroups], i=0; i < naddgroups; i++) *groups_p++ = *addgroups_p++; ngroups += naddgroups; } else if (localinfo.ngroups != GROUPS_NOTSET) { /* There are some explicit local groups=xxx */ if (Setgroups(localinfo.ngroups, localinfo.groups) == -1) return Error(1, 0, "Failed to set supplementary groups list: "); } else if (globalinfo.ngroups != GROUPS_NOTSET) { /* There is an explicit global groups=xxx or addgroups=xxx */ if (Setgroups(globalinfo.ngroups, globalinfo.groups) == -1) return Error(1, 0, "Failed to set supplementary groups list: "); } else { /* Default is no supplementary groups */ if (Setgroups(0, localinfo.groups) == -1) return Error(1, 0, "Failed to clear supplementary groups list: "); } return 0; } #endif /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Set user, group, and supplementary groups according to the specified args */ /* Side effect: if localinfo.user is numeric, change it to a name */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ int set_u_g() { /* Return 0 on success, -1 on failure */ SETGRENT_T setgrent(); void endgrent(); int i, j, k = -1; int found_gid = -1, found_egid = -1; struct passwd *pw = NULL; int use_setreuid; /* true iff we need to use setreuid() */ int use_setregid; /* true iff we need to use setregid() */ uid_t uid_pw_uid = -1, euid_pw_uid = -1; gid_t u_g_pw_gid = -1; char *uid_pw_name = NULL, *euid_pw_name = NULL; /* * Start by looking up user id's and group id's. We'll assign * actual values after we've looked it all up. */ /* First, get the group id's specified by {e,}gid=xxx */ if (*localinfo.group) { found_gid = findgid(1, localinfo.group); if (found_gid == -1) { return Error(0, 0, "%t\n\tCan't set gid: no such group as `%s' in group file.\n", localinfo.group); } } if (*localinfo.egroup) { found_egid = findgid(1, localinfo.egroup); if (found_egid == -1) { return Error(0, 0, "%t\n\tCan't set egid: no such group as `%s' in group file.\n", localinfo.egroup); } } /* Second, get the password entries specified by {e,}uid=xxx, u+g=xxx */ if (*localinfo.u_g && !*localinfo.user) { /* u+g=zzz was used, but uid=xxx was not. So, set user from u+g. */ strcpy(localinfo.user, localinfo.u_g); } if (*localinfo.user) { pw = getpwentry(1, localinfo.user); if (!pw) return -1; uid_pw_uid = pw->pw_uid; uid_pw_name = strdup(pw->pw_name); if (!uid_pw_name) return Error(0, 0, "%t\n\tfailed to malloc space for uid_pw_name=<%s>\n", pw->pw_name); } if (*localinfo.euser) { pw = getpwentry(1, localinfo.euser); if (!pw) return -1; euid_pw_uid = pw->pw_uid; euid_pw_name = strdup(pw->pw_name); if (!euid_pw_name) return Error(0, 0, "%t\n\tfailed to malloc space for euid_pw_name=<%s>\n", pw->pw_name); } if (*localinfo.u_g) { pw = getpwentry(1, localinfo.u_g); if (!pw) return -1; u_g_pw_gid = pw->pw_gid; } pw = NULL; if (*localinfo.group && *localinfo.u_g) { return Error(0, 0, "%t\n\tCan't mix options gid=xxx and u+g=yyy in one entry."); } /* * OK, now we have all the uid/gid info. Select uid's and gid's, * and select the appropriate set of functions to do the assignments. */ /* UID defaults: */ userinfo.new_uid = userinfo.caller.pw_uid; use_setreuid = 0; /* don't have to use setreuid() */ if (*localinfo.euser) { /* euid=xxx was used */ userinfo.new_euid = euid_pw_uid; if (strcmp(euid_pw_name, localinfo.euser) != 0) { /* localinfo.euser must be numeric; convert it to string */ strcpy(localinfo.euser, euid_pw_name); } use_setreuid = 1; /* have to use setreuid() */ } if (*localinfo.user) { /* uid=xxx or u+g=xxx was used */ userinfo.new_uid = uid_pw_uid; if (strcmp(uid_pw_name, localinfo.user) != 0) { /* localinfo.user must be numeric; convert it to string */ strcpy(localinfo.user, uid_pw_name); } } /* GID defaults: */ userinfo.new_gid = userinfo.caller.pw_gid; use_setregid = 0; /* don't have to use setreuid() */ if (*localinfo.egroup) { /* egid=xxx was used */ userinfo.new_egid = found_egid; use_setregid = 1; /* have to use setreuid() */ } if (*localinfo.group) { /* gid=xxx was used */ userinfo.new_gid = found_gid; } else if (*localinfo.u_g) { userinfo.new_gid = u_g_pw_gid; } /* Set supplementary groups */ if (set_suppl_groups() == -1) return -1; /* Now set uid & gid */ if (use_setregid) { #ifdef HAVE_SETREGID if ((i=setregid(userinfo.new_gid, userinfo.new_egid)) == -1) { return Error(1, 0, "setregid(gid=%d, egid=%d) failed: ", userinfo.new_gid, userinfo.new_egid); } else if ((j=getgid()) != userinfo.new_gid || (k=getegid()) != userinfo.new_egid) { return Error(0, 0, "setregid(gid=%d,egid=%d) returned 0, but getgid()=%d,getegid()=%d!", userinfo.new_gid, userinfo.new_egid, j, k); } #else return Error(0, 0, "Can't use egid=xxx option, because this host doesn't offer the setregid() function"); #endif } else if (*localinfo.group || *localinfo.u_g) { if ((i=setgid(userinfo.new_gid)) == -1) { return Error(1, 0, "setgid(gid=%d) failed: ", userinfo.new_gid); } else if ((j=getgid()) != userinfo.new_gid) { return Error(0, 0, "setgid(gid=%d) returned %d, but getgid() returned %d!", userinfo.new_gid, i, j); } } if (use_setreuid) { #ifdef HAVE_SETREUID if ((i=setreuid(userinfo.new_uid, userinfo.new_euid)) == -1) { return Error(1, 0, "setreuid(uid=%d, euid=%d) failed: ", userinfo.new_uid, userinfo.new_euid); } else if ((j=getuid()) != userinfo.new_uid || (k=geteuid()) != userinfo.new_euid) { return Error(1, 0, "setreuid(uid=%d,euid=%d) returned 0, but getuid()=%d,geteuid()=%d!", userinfo.new_uid, userinfo.new_euid, j, k); } #else return Error(1, 0, "Can't use euid=xxx option, because this host doesn't offer the setreuid() function"); #endif } else if (*localinfo.user || *localinfo.u_g) { if ((i=setuid(userinfo.new_uid)) == -1) { return Error(1, 0, "setuid(uid=%d) failed: ", userinfo.new_uid); } else if ((j=getuid()) != userinfo.new_uid) { return Error(1, 0, "setuid(uid=%d) returned %d, but getuid() returned %d!", userinfo.new_uid, i, j); } } if (uid_pw_name) free(uid_pw_name); if (euid_pw_name) free(euid_pw_name); return 0; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Puts the encrypted password in userinfo.encr, and the salt in userinfo.salt. * Returns 0 on success, -1 on failure to obtain the password. */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifdef SUNOS5 #define HAVE_G_E_PW int get_encrypted_pw() { /* Shadow passwords are always used on Sunos 5.x */ struct spwd *caller_pw; if (!(caller_pw = getspnam(userinfo.caller.pw_name))) { authInitErrno = errno; sprintf(authInitMsg1, "Failed to obtain shadow password entry for user %s", userinfo.caller.pw_name); return -1; } strcpy(userinfo.encr, caller_pw->sp_pwdp); return get_setting(); } #endif #ifdef _HPUX_SOURCE #define HAVE_G_E_PW int get_encrypted_pw() { if(iscomsec()) { struct pr_passwd *caller_pw; if (!(caller_pw = getprpwnam(userinfo.caller.pw_name))) { authInitErrno = errno; sprintf(authInitMsg1, "Failed to obtain Trusted Computing Base entry for user %s", userinfo.caller.pw_name); return -1; } strcpy(userinfo.encr, caller_pw->ufld.fd_encrypt); } else { struct passwd *caller_pw; /* See if we can do shadow password lookup for HPUX 9.x. * The rule is that if /.secure/etc/passwd exists, we have to use it; * otherwise, fall through to regular password file lookup. */ static struct stat st; if (stat("/.secure/etc/passwd", &st) == 0) { /* Shadow password file exists; use it */ struct s_passwd *caller_pw; if (!(caller_pw = getspwnam(userinfo.caller.pw_name))) { authInitErrno = errno; sprintf(authInitMsg1, "Failed to obtain shadow password entry for user %s", userinfo.caller.pw_name); return -1; } strcpy(userinfo.encr, caller_pw->pw_passwd); } else { /* Fall through to regular password file lookup. */ strcpy(userinfo.encr, userinfo.caller.pw_passwd); } } return get_setting(); } #endif #ifdef SCO #define HAVE_G_E_PW int get_encrypted_pw() { struct passwd *caller_pw; struct spwd *caller_ps; if (!(caller_pw = getpwnam(userinfo.caller.pw_name))) { sprintf(authInitMsg1, "No password entry for user %s.\n", userinfo.caller.pw_name); return -1; } /* SCO 3.2v4 has "x" in password field to indicate shadow password * file has to be consulted. */ if (strcmp(caller_pw->pw_passwd, "x") == 0) { /* Shadow password in use... */ if (!(caller_ps = getspnam(userinfo.caller.pw_name))) { authInitErrno = errno; sprintf(authInitMsg1, "Failed to obtain shadow password entry for user %s", userinfo.caller.pw_name); return -1; } strcpy(userinfo.encr, caller_ps->sp_pwdp); } else { /* Fall through to regular password file lookup. */ strcpy(userinfo.encr, caller_pw->pw_passwd); } return get_setting(); } #endif #ifdef Digital_UNIX #define HAVE_G_E_PW int get_encrypted_pw() { struct passwd *caller_pw; struct pr_passwd *caller_prpass; if (!(caller_pw = getpwnam(userinfo.caller.pw_name))) { sprintf(authInitMsg1, "No password entry for user %s.\n", userinfo.caller.pw_name); return -1; } /* Digital Unix has "x" in password field to indicate shadow password * file has to be consulted. */ if (strcmp(caller_pw->pw_passwd, "x") == 0) { /* Protected (shadow) password in use... */ if (!(caller_prpass = getprpwnam(userinfo.caller.pw_name))) { authInitErrno = errno; sprintf(authInitMsg1, "Failed to obtain shadow password entry for user %s", userinfo.caller.pw_name); return -1; } strcpy(userinfo.encr, caller_prpass->ufld.fd_encrypt); } else { /* Fall through to regular password file lookup. */ strcpy(userinfo.encr, caller_pw->pw_passwd); } return get_setting(); } #endif #ifdef __linux__ #define HAVE_G_E_PW int get_encrypted_pw() { /* Use /etc/shadow if it exists; else fall back on std */ static struct stat st; if (stat("/etc/shadow", &st) == 0) { struct spwd *spwd = 0L; if (!(spwd = getspnam(userinfo.caller.pw_name))) { /* Gordon Lack notes: Linux doesn't use the shadow file for NIS * passwords, so if you get ENOENT or no error, then try the * local password file. */ if (errno == ENOENT || errno == 0) { strcpy(userinfo.encr, userinfo.caller.pw_passwd); } else { authInitErrno = errno; sprintf(authInitMsg1, "Failed to obtain shadow password entry for user %s", userinfo.caller.pw_name); return -1; } } else { /* got shadow entry */ strcpy(userinfo.encr, spwd->sp_pwdp); } } else { /* Fall through to regular password file lookup. */ strcpy(userinfo.encr, userinfo.caller.pw_passwd); } return get_setting(); } #endif #ifdef __FreeBSD__ #define HAVE_G_E_PW int get_encrypted_pw() { /* getpwnam() and getpwuid() will have read the shadow passwd * if our effective uid == root; no need to use getspnam() or the like. * But if the encrypted pw is "*", then we didn't get the real pw. */ strcpy(userinfo.encr, userinfo.caller.pw_passwd); if (strcmp(userinfo.encr, "*") == 0) { sprintf(authInitMsg1, "Can't get encrypted password, \ or there is no password, for user %s", userinfo.caller.pw_name); return -1; } return get_setting(); } #endif #ifdef __OpenBSD__ #define HAVE_G_E_PW int get_encrypted_pw() { /* getpwnam() and getpwuid() will have read the shadow passwd * if our effective uid == root; no need to use getspnam() or the like. * But if the encrypted pw is "*", then we didn't get the real pw. */ strcpy(userinfo.encr, userinfo.caller.pw_passwd); if (strcmp(userinfo.encr, "*") == 0) { sprintf(authInitMsg1, "Can't get encrypted password, \ or there is no password, for user %s", userinfo.caller.pw_name); return -1; } return get_setting(); } #endif #ifndef HAVE_G_E_PW int get_encrypted_pw() { /* Vanilla password file lookup */ strcpy(userinfo.encr, userinfo.caller.pw_passwd); strncpy(userinfo.salt, userinfo.caller.pw_passwd, 2); return get_setting(); } #endif /* * Read the userinfo.encr (encrypted passwd field) and pull out the data * needed for the "salt" or "setting" argument to crypt(). * This version implements the following rule: * o if encr begins with "$", the entire encr field should be used * (crypt() will be responsible for extracting the data it needs); * o otherwise, if encr begins with "_", use the first 9 chars * (crypt() will discard the leading "_" and use the next 8 characters); * o otherwise, the salt is the first two chars. * * Returns: * -1 (plus Error()) on failure * 0 on success. */ int get_setting() { if (sizeof(userinfo.salt) <= strlen(userinfo.encr)) { return Error(0, 0, "Compilation error: the userinfo.salt field is too short (%d chars) to hold a copy of the userinfo.encr field (%d chars) for user %s", sizeof(userinfo.salt), strlen(userinfo.encr), userinfo.caller.pw_name); } if (userinfo.encr[0] == '$') { strcpy(userinfo.salt, userinfo.encr); } else if (userinfo.encr[0] == '_') { /* crypt() _might_ parse the encr field to pull off the necessary * salt, but we don't make that assumption. */ strncpy(userinfo.salt, userinfo.encr, 9); userinfo.salt[9] = '\0'; } else { /* Salt is first two chars. */ strncpy(userinfo.salt, userinfo.encr, 2); userinfo.salt[2] = '\0'; } return 0; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Returns ptr to string with name of desired authentication method. */ char * auth_name() { if (!localinfo.authinfo.required) { return "None"; } else if (localinfo.authinfo.method == SUPER_AUTH_PASSWORD) { return "Password"; } else if (localinfo.authinfo.method == SUPER_AUTH_PAM) { return "PAM"; } else { Error(0, 1, "auth_name: don't recognize auth method %d!\n", localinfo.authinfo.method); } /* NOTREACHED */ return "Unknown!"; /* to make gcc -Wall happy... */ } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Checks if authentication is needed, and does so if needed. * Expects that the encrypted password is already in userinfo.{encr,salt}, * so that this can be run after switching to unpriviledged uid. * Returns 0 on success, -1 on error. * The timestamp directory faces the same problem as the logfile: if the * administrator wants to share an NFS-mounted directory across hosts * on which root is translated to nobody for NFS access, we have to be * able to create the timestamp file under a special uid. This is done * just as in open_writer(): we fork, setuid(), and do the file * manipulation in the child. This allows us to implement a special uid * for the timestamp file, without needing the operating system to * offer saved uid's or interprocess file-descriptor passing, etc. */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ int check_auth(cmd) char *cmd; { char file[MAXPATHLEN]; struct stat st; int l, istat, file_exists, got_auth, err, timestamp_creatok; int timed_out = 0; int status; pid_t pid, child; char mkdirMsg[1000]; char *authuser; if (!localinfo.authinfo.required) return 0; /* don't need authentication */ if (*authInitMsg1 || *authInitMsg2) { /* A message was generated during authentication initialization, * way back before the super.tab file was read. It was only * relevant if the command required authorization... well, this * command requires user authentication, so print the message now. */ if (*authInitMsg1) { if (authInitErrno) { errno = authInitErrno; Error(1, 0, "%s: ", authInitMsg1); } else { Error(0, 0, "%s", authInitMsg1); } } if (*authInitMsg2) { Error(0, 0, "%s", authInitMsg2); } } /* Create or update the timestamp file even if the lifetime is 0 * (always ask for password). We do this because the user may * execute _another_ command which has a password expiry > 0, * and which will be happy to use the password that was already * entered with the 0-lifetime command. */ child = fork(); if (child == -1) { Error(1, 0, "Failed to create child for timestamp processing: "); return -1; } else if (child > 0) { /* In parent -- wait to see if the child succeeded */ while ((pid = wait(&status)) > 0 && pid != child) { if (pid == globalinfo.log.pid) { Error(0, 0, "Warning: logging process died -- logging to file has stopped."); globalinfo.log.pid = -1; } else { Error(0, 0, "Wait() surprised! check_pass() received pid %d;\n\t\ expected child pid = %d; waiting for correct pid...\n", pid, child); } } if (pid == -1) { /* wait() failed */ Error(1, 0, "Waiting for timestamp creation process: "); return -1; } else if (status == 0) { /* child succeeded */ return 0; } else if (status != 0) { /* child failed to authenticate user */ Error(0, 0, "Authentication failed\n"); return -1; } /* NOTREACHED */ } /* * If here, must be in child. * Unless this command specifies a timeout > 0, don't generate * error messages if we fail to store timestamp info. * We'll use exit code 1 for failure to authenticate; * otherwise we'll exit 0 (success). */ if (child != 0) { Error(0, 1, "Internal error: child=%d; should be 0!\n", child); } /* * setuid, then make and/or test the directory */ if (*localinfo.authinfo.ts_user != '\0') { stringcopy(localinfo.user, localinfo.authinfo.ts_user, sizeof(localinfo.user)); *localinfo.group = '\0'; *localinfo.u_g = '\0'; if (set_u_g() == -1) { if (localinfo.authinfo.timeout > 0) { Error(1, 0, "failed to setuid to user=%s before setting timestamp file: ", localinfo.user); } exit(2); } } /* Make the timestamp directory name */ timestamp_creatok = 1; mkdirMsg[0] = '\0'; if (!makedirname(TIMESTAMP_DIR, globalinfo.authinfo.perhost ? userinfo.hostname : "", file, &err, mkdirMsg)) { timestamp_creatok = 0; if (localinfo.authinfo.timeout > 0 && mkdirMsg[0]) { if (err) { errno = err; Error(1, 0, "Warning: can't record timestamp: %s: ", mkdirMsg); } else { Error(0, 0, "Warning: can't record timestamp: %s", mkdirMsg); } } } /* Make the timestamp directory */ if (timestamp_creatok) { mkdirMsg[0] = '\0'; if (makedir(file, &err, mkdirMsg) == -1) { timestamp_creatok = 0; if (localinfo.authinfo.timeout > 0 && mkdirMsg[0]) { if (err) { errno = err; Error(1, 0, "Warning: can't record timestamp: %s: ", mkdirMsg); } else { Error(0, 0, "Warning: can't record timestamp: %s", mkdirMsg); } } } } /* Make the file in the timestamp directory */ if (timestamp_creatok) { l = strlen(file) + 1 + strlen(userinfo.caller.pw_name); if (l >= MAXPATHLEN) { if (localinfo.authinfo.timeout > 0) { Error(1, 0, "Can't create timestamp file: would exceed MAXPATHLEN = %d\n", MAXPATHLEN); } timestamp_creatok = 0; } } if (timestamp_creatok) { strcat(file, "/"); strcat(file, userinfo.caller.pw_name); istat = stat(file, &st); if (istat != 0 && errno != ENOENT) { if (localinfo.authinfo.timeout > 0) { Error(1, 0, "Failed to stat timestamp file `%s': ", file); } timestamp_creatok = 0; } } if (timestamp_creatok) { file_exists = (istat == 0); if (file_exists) { timed_out = (localinfo.authinfo.timeout < 1) || ((time(NULL)-st.st_mtime) > localinfo.authinfo.timeout*60); } } got_auth=0; if (localinfo.authinfo.user[0]) { authuser = localinfo.authinfo.user; /* authuser=xxx local opt */ } else if (globalinfo.authinfo.user[0]) { authuser = globalinfo.authinfo.user; /* authuser=xxx global opt */ } else { authuser = userinfo.caller.pw_name; /* default: caller's pw */ } if (!timestamp_creatok || !file_exists || timed_out) { switch (localinfo.authinfo.method) { case SUPER_AUTH_PASSWORD: got_auth = (get_password(cmd, userinfo.caller.pw_name, authuser, userinfo.salt, userinfo.encr) == 1); break; case SUPER_AUTH_PAM: #if WITH_PAM got_auth = (get_pam(cmd, userinfo.caller.pw_name, authuser) == 1); #else Error(0, 1, "Auth method is PAM, but this copy of super \ was compiled without PAM support!"); #endif break; default: Error(0, 1, "Internal error in check_auth: \ unknown auth method %d!\n", localinfo.authinfo.method); } if (!got_auth) return -1; } /* NOTE: A race condition is possible between two super's, with the * worst-case effect of an error message and failure to run the * requested command. */ /* If file exists, and we haven't (a) gotten the password again, or * (b) supposed to automatically refresh the timestamp, do nothing to * the file except ensure that we own it. * Otherwise create the file (unlink it first if it exists). */ if (!timestamp_creatok) { /* do nothing: we failed in setting up the timestamp file, * so don't try to update it. */ } else if (file_exists && !(got_auth || localinfo.authinfo.renewtime)) { if (st.st_uid != geteuid()) return Error(0, 0, "Timestamp file `%s' is owned by uid=%d, but expected owner=%d.\n\ \tN.B. If you recently changed the value of timestampuid=xxx, all existing\n\ \tfiles in the timestamp directory _may_ have the wrong owner; delete them.\n\ \t(No security hole appears when you delete a timestamp file.)\n", file, st.st_uid, geteuid()); } else { if (file_exists) { if (unlink(file) != 0) return Error(1, 0, "Failed to unlink() timestamp file `%s': ", file); } if (open(file, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, 0200) == -1) return Error(1, 0, "Failed to open() timestamp file `%s': ", file); } exit(0); /* UNREACHABLE */ Error(0, 1, "Unreachable code!\n"); return 0; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Encrypt a password. Returns value from crypt() or similar. */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ char * docrypt(prompt, salt) char *prompt; char *salt; { char *crypt(); char *getpass(); char buf[300]; int n; n = s_getpass(prompt, use_stdin, buf, sizeof(buf)); if (n < 0) { Error(0, 1, "Failed to get password.\n"); } else if (n >= sizeof(buf)) { Error(0, 1, "Buffer too small (%d chars) to hold input password!\n", sizeof(buf)-1); } #ifdef _HPUX_SOURCE if (iscomsec()) { #if (HPUX_MAJOR == 10) return bigcrypt(buf, salt); #endif #if (HPUX_MAJOR == 11) return bigcrypt(buf, salt); #endif return crypt(buf, salt); } else { return crypt(buf, salt); } #else return crypt(buf, salt); #endif } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Gets a user's encrypted password. Returns -1 on failure, +1 on success */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ int get_password(cmd, caller, user, salt, encr) char *cmd; char *caller; /* the person who invoked super */ char *user; /* the person whose authentication is required */ char *salt; char *encr; { /* No such file or password timed out -- get password */ int ntry, match; char msg[500]; char *encrypted = NULL; if (strcmp(encr, "") == 0) { return Error(0, 0, "Command requires a password, but user `%s' has no password\n", user); } for (ntry=0, match=0; ntry < MAXTRY && !match; ntry++) { if (ntry == 0) { if (localinfo.authinfo.prompt && localinfo.authinfo.prompt[0]) { stringcopy(msg, do_variables(localinfo.authinfo.prompt), sizeof(msg)); } else if (strcmp(caller, user) == 0) { (void) sprintf(msg, "Your password is required for super command `%.400s'...\nPassword: ", cmd); } else { (void) sprintf(msg, "%c%s's password is required for super command `%.400s'...\nPassword: ", toupper(*user), user+1, cmd); } } else { strcpy(msg, "Password incorrect\nPassword: "); } encrypted = docrypt(msg, salt); if (encr && encrypted) { match = (strcmp(encr, encrypted) == 0); } else { match = 0; } } if (!match) return Error(0, 0, "Password incorrect\n"); return 1; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Looks up a group name or number (as a text string), returns gid. * Accepts special names (e.g. or ). * Returns -1 if no such group. */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ int findgid(allowbrackets, grouplabel) int allowbrackets; /* accept names like */ char *grouplabel; /* name or numeric form */ { struct group *gp; SETGRENT_T setgrent(); void endgrent(); int numeric_gid; int found_gid, is_numeric=0; int l=strlen(grouplabel); char c; if (allowbrackets && grouplabel[0] == '<' && grouplabel[l-1] == '>') { /* Translate special name */ struct passwd *pw = getpwentry(1, grouplabel); if (!pw) return -1; else return pw->pw_gid; } is_numeric = (sscanf(grouplabel, "%d%c", &numeric_gid, &c) == 1); if (is_numeric) return numeric_gid; /* Grouplabel didn't look like a number (according to sscanf), * so look up its name. */ setgrent(); for (found_gid = -1, gp = getgrent(); gp; gp = getgrent()) { if (strcmp(grouplabel, gp->gr_name) == 0) { /* Found the gid in the group file */ found_gid = gp->gr_gid; break; } } endgrent(); return found_gid; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Adds condition to condition list. * returns -1 on syntax error, malloc error, etc; * returns 0 otherwise. */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ int InsertCondition(condition, s, isglobal) char *condition; /* condition to insert: xxx~yyy */ char *s; /* pts to yyy in condition */ int isglobal; /* Is this a per-command or global condition? */ { char **globlist; TimeList *tl; int i; int invert = (*condition == '!'); if ( invert ) condition++; /* All conditions accept {a,b,c} or a,b,c as lists. * Form the globlist and pass along... */ /* Do brace globbing */ if ((i=globbraces(s, 1, &globlist)) != 0) /* Local Condition */ return Error(0, 0, "%tMissing `%c'.\n", i); if (STRMATCH3("time", condition, s-1)) { tl = isglobal ? &globalinfo.timeafter : &localinfo.time; if (InsertTimeList(s, globlist, tl, isglobal ? "global" : "local", invert) == -1) return -1; } else if (STRMATCH3("user", condition, s-1)) { if (InsertUserList(s, globlist, &localinfo.userpats, &localinfo.origtext, invert) == -1) return -1; } else { return Error(0, 0, "%t\n\tInternal error: unrecognized condition <%s>.\n", condition); } return 0; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Add a user/group/host pattern to a list. * returns -1 on syntax error, malloc error, etc; * returns 0 otherwise. */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ int InsertUserList(wd, wdlist, sl, otl, invert) char *wd; /* Pattern to match; must NOT have leading '!'; braces ok; * disallow '<', '>'. */ char **wdlist; /* brace-expanded u/g/h list */ Simple2List *sl;/* Insert user list elements (i.e. argument wdlist) * at sl->next. */ SimpleList *otl;/* Insert original text (i.e. argument wd) * at otl->next. */ int invert; /* Inverts the test */ { int iwd; char *tok, *s; SimpleList *new; Simple2List *new2; /* Check for illegal characters */ if ((s=strchr(wd, '>')) || (s=strchr(wd, '<'))) { if (s-wd == 4 && strncmp(wd, "time", 4) == 0) { return Error(0, 0, "%t\n\tPermittedUser patterns may not use '>' or '<';\n\ \tyou used '%s'; perhaps you meant to write 'time~%s'\n", wd, s); } else { return Error(0, 0, "%t\n\tPermittedUser patterns may not use '>' or '<';\n\ \tyou used '%s'.\n", wd); } } new = (SimpleList *) malloc(sizeof(SimpleList)); if (!new) return Error(0, 0, "%t\n\tFailed to malloc space for PermittedUser\n"); new->next = otl->next; new->pat = (char *) malloc(strlen(wd) + 1); if (!new->pat) return Error(0, 0, "%t\n\tFailed to malloc space for PermittedUser pat\n"); strcpy(new->pat, wd); otl->next = new; for (iwd=0; (tok=wdlist[iwd]); iwd++) { new2 = (Simple2List *) malloc(sizeof(Simple2List)); if (!new2) return Error(0, 0, "%t\n\tFailed to malloc space for PermittedUser\n"); new2->next = sl->next; new2->other = otl->next; new2->pat = (char *) malloc(strlen(tok) + (invert ? 2 : 1)); if (!new2->pat) return Error(0, 0, "%t\n\tFailed to malloc space for PermittedUser pat\n"); if (invert) { *new2->pat = '!'; strcpy(new2->pat+1, tok); } else { strcpy(new2->pat, tok); } sl->next = new2; } return 0; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Match a list of user/group/host pattern against the present user. */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ void match_ugh_user(sl, isglobal) Simple2List *sl;/* A list of user pats to match against, starting at sl->next */ int isglobal; /* !0 means its from a global def */ { /* Checks if user is matched against each elt in sl. * Sets matches.match_user if user matches; sets it to 1 if the last * match is non-inverting, 0 otherwise. * BUT! the list created by the InsertUserList function * is in reverse order, so we only need to find the first * entry in the list that is a match (+ or -) and stop there! */ int invert, match; int check_ugh P__((char *, char *)); for (match=0, sl=sl->next; sl && !match; sl=sl->next) { invert = *sl->pat == '!'; if (check_ugh(sl->other->pat, invert ? sl->pat+1 : sl->pat) == 0) { match = 1; matches.user = invert ? 0 : 1; if (debug || it_came_from_cmdline) (void) fprintf(stderr, "\tPermission %s: %s pattern %suser~%s\n", invert ? "denied" : "allowed", isglobal ? "global" : "per-cmd", invert ? "!" : "", sl->other->pat); } else if (debug || it_came_from_cmdline) { (void) fprintf(stderr, "\tNot applicable: %s pattern %suser~%s\n", isglobal ? "global" : "per-cmd", invert ? "!" : "", sl->other->pat); } } } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Check pattern against a hostname. If the hostname is fully-qualified, * then try stripping off each of the domains to find a match. * Return -1 on failure to match; 0 on success. */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ int check_host(pat, host) char *pat, *host; { int is_netgroup = 0; int match; char *p, *dotp; if (*pat == '+') { is_netgroup = 1; ++pat; } match = (is_netgroup ? netgrp_h_compare(pat, host) : (*pat_compare)(host)); dotp = strrchr (host, '.'); while (dotp && !match) { *dotp = 0; match = (is_netgroup ? netgrp_h_compare(pat, host) : (*pat_compare)(host)); p = strrchr (host, '.'); *dotp = '.'; dotp = p; } return (match ? 0 : -1); } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Try to match a string to a pattern. */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ int match_pattern(match, do_glob, str, pattern) int match; /* Return input value of match on failure; 1 on success */ int do_glob; /* 0: no brace globbing; * 1: brace glob; * >1: wrap in braces, then brace-glob. */ char *str; char *pattern; { int i, ipat, wrap; char *tok, tokbuf[1000]; char **patlist, *pat1list[2]; char chkbuf[1024]; if (do_glob != 0) { /* Do brace globbing on the pattern */ wrap = (do_glob > 1) ? 1 : 0; if ((i=globbraces(pattern, wrap, &patlist)) != 0) { /* MatchPat */ Error(0, 0, "%tMissing `%c'.\n", i); return match; } } else { pat1list[0] = pattern; pat1list[1] = NULL; patlist = pat1list; } for (ipat=0; (tok=patlist[ipat]); ipat++) { strcpy(tokbuf, tok); anchor(tok, chkbuf); /* Anchor all matches */ if ((*pat_compile)(chkbuf) != NULL) { Error(0, 0, "%t\n\tBad command pattern: `%s'.\n", pattern); return match; } else if ((*pat_compare)(str) == 1) { if (debug) (void) fprintf(stderr, "\tMatched user's command=%s to CmdPattern=%s\n", str, pattern); return 1; } else if (debug) { (void) fprintf(stderr, "\tNo match user's command=%s to CmdPattern=%s\n", str, pattern); } } return match; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Check a single user/group/host string * Return -1 on failure to match; 0 on success. */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ int check_ugh(origtext, token) char *origtext; /* original text -- for error messages */ char *token; /* user/group/host pattern */ { char chkbuf[1024]; char *userpat, *grouppat, *hostpat; char *colon; int match, i; if (strlen(token) > sizeof(chkbuf)-4) { return Error(0, 1, "%tCan't handle patterns larger than %d chars.\n", sizeof(chkbuf)-4); } /* Split into user:group@host; check host part first (if it exists) */ if ((hostpat = strchr(token, '@'))) { if (hostpat[1] == 0) return Error(0, 0, "%tMissing hostname in pattern `%s'.\n", origtext); *hostpat++ = 0; match = -1; if (hostpat[0] == '+') { #ifdef HAVE_INNETGR if (hostpat[1] == 0) return Error(0, 0, "%tMissing netgroupname in pattern `%s'.\n", origtext); match = check_host(hostpat, userinfo.hostname); #else return Error(0, 0, "%thostnames may not begin with `+' since this super() was compiled\n\ without -DHAVE_INNETGR.\n"); #endif } else { strtolower(hostpat); anchor(hostpat, chkbuf); /* Force all matches to be anchored */ if ((*pat_compile)(chkbuf) != NULL) return Error(0, 0, "%tbad host pattern: `%s'.\n", origtext); } if (match == -1) match = check_host(hostpat, userinfo.lc_hostname); if (debug > 1) fprintf(stderr, "\thost pattern <%s> %s user's host <%s>\n", hostpat, (match == -1) ? "did not match" : "matched", userinfo.lc_hostname); if (match == -1) return -1; } colon = grouppat = strchr(token, ':'); userpat = token; if (*token == '\0' && !hostpat) { /* Nothing in pattern?! */ return Error(0, 0, "%t\n\tUnacceptable pattern `%s'.\n", origtext); } else if (*token == '\0') { userpat = grouppat = "^.*$"; /* only hostname given */ } else if (grouppat && *(grouppat+1)) { /* pat is "uuu:ggg or ":ggg" */ if (token == grouppat) userpat = "^.*$"; /* pat is ":ggg" */ *grouppat++ = '\0'; } else { /* pat is "uuu" or "uuu:" */ if (grouppat) *grouppat = '\0'; /* pat is "uuu:" */ grouppat = "^.*$"; } if (strchr(grouppat, ':')) return Error(0, 0, "%t\n\tGroup pattern `%s' contains a colon!\n", grouppat); if (globalinfo.group_slash == 0 && strchr(grouppat, '/')) return Error(0, 0, "%t\n\tFormat error in super.tab file: \ group pattern `%s' contains a slash.\n\ \tPerhaps you meant to use Cmd::Filename, but forgot one colon,\n\ \tso it looks like User:Group? If you really need to allow\n\ \tslashes in group patterns, use global option group_slash=y.\n", grouppat); #ifdef HAVE_INNETGR if (userpat[0] == '+') { if (userpat[1] == 0) return Error(0, 0, "%tMissing netgroupname in pattern `%s'.\n", origtext); match = netgrp_u_compare(&userpat[1], userinfo.caller.pw_name); } else #endif { anchor(userpat, chkbuf); /* Anchor all matches */ if ((*pat_compile)(chkbuf) != NULL) return Error(0, 0, "%t\n\tbad user pattern: `%s'.\n", origtext); match = (*pat_compare)(userinfo.caller.pw_name); #ifdef MATCH_DECIMAL_UID if (match != 1) { /* Enabling MATCH_DECIMAL_UID allows the userpat to be * numeric, as an alternative to being interpreted as a * user name: after checking the username, we check if the * user's uid, as a decimal text value, matches the user * pattern userpat. */ char buf[20]; (void) sprintf(buf, "%d", userinfo.caller.pw_uid); match = (*pat_compare)(buf); } #endif } if (debug > 1) fprintf(stderr, "\tuser pattern <%s> %s username <%s>\n", userpat, (match != 1) ? "did not match" : "matched", userinfo.caller.pw_name); if (match != 1) return -1; anchor(grouppat, chkbuf); i = ingroup(userinfo.caller.pw_name, userinfo.caller.pw_gid, chkbuf); if (i == -1) return Error(0, 0, "%t\n\tbad group pattern\n", origtext); if (debug > 1) fprintf(stderr, "\tuser <%s> is %sa member of group <%s>\n", userinfo.caller.pw_name, (i != 1) ? "not " : "", grouppat); if (i != 1) return -1; return 0; /* Success! */ } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* Determines if user's group matches a group pattern. */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ int ingroup(user, gid, gp_pat) char *user; gid_t gid; char *gp_pat; /* pattern to match */ { /* Use: * ingroup(user, gid, gp_pat) * Returns: * 1 if the user is in a group matching the regex pattern gp_pat. * 0 if the user isn't in a group matching the pattern. * -1 if pattern failed to compile. * SIDE-EFFECT: uses pat_compile/pat_compare! * -- messes up caller's use of same! * Examples: * ingroup("joe", joes_gid, "xyz") * returns !0 if user joe is in group "xyz". * ingroup("joe", joes_gid, "xy.*") * returns !0 if user joe is in any group matching "xy.*". */ struct group *gp; char **mem; char buf[20]; SETGRENT_T setgrent(); void endgrent(); if ((*pat_compile)(gp_pat) != (char *)0 ) return -1; /* Search group file for groups user is in. For each group of which * the user is a member, test a match to the pattern. */ setgrent(); for (gp = getgrent(); gp; gp = getgrent()) { /* The gr_mem list only shows usernames added in the /etc/group file, * and not any users assigned to the group in the passwd file. * Thus discover group membership by first checking the user's * group from the password file (gp->gr_gid) against this group's * gid, then check to see if this user is in the gp->gr_mem list. */ if (gid != gp->gr_gid) { for (mem = gp->gr_mem; *mem ; mem++) if (strcmp(*mem, user) == 0) break; if (!*mem) continue; /* not in group */ } /* if here, the user is in group gp; now check if group * name gp->gr_name matches group pattern gp_pat. */ if ((*pat_compare)(gp->gr_name) == 1) { /* successful match -- user is in a group that matches gp_pat */ endgrent(); return 1; } #ifdef MATCH_DECIMAL_GID else { /* Enabling MATCH_DECIMAL_GID allows the gp_pat to be * numeric, as an alternative to being interpreted as a * group name: we check if the group id gp->gr_gid, as a * decimal text value, matches the group pattern gp_pat. */ (void) sprintf(buf, "%d", gp->gr_gid); if ((*pat_compare)(buf) == 1){ /* successful match -- user is in a group that matches gp_pat */ endgrent(); return 1; } } #endif } #ifdef MATCH_DECIMAL_GID /* We haven't found any group from /etc/group to which we belong that * matches the pattern. It is possible that the user's group id from the * password file isn't in the /etc/group file at all, in which case the * user's group won't have matched the pattern since we've only checked * /etc/group entries so far. Now check the numeric id from the * /etc/passwd file against the pattern. */ (void) sprintf(buf, "%d", gid); if ((*pat_compare)(buf) == 1){ endgrent(); return 1; } #endif endgrent(); return 0; }