/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef TEXT_DOMAIN /* Should be defined by cc -D */ #define TEXT_DOMAIN "SYS_TEST" #endif extern int cannot_audit(int); static char *pathsearch(char *); static int getrealpath(const char *, char *); static int checkattrs(char *, int, char *[]); static void sanitize_environ(); static uid_t get_uid(char *); static gid_t get_gid(char *); static priv_set_t *get_privset(const char *); static priv_set_t *get_granted_privs(uid_t); static void get_default_privs(const char *, priv_set_t *); static void get_profile_privs(char *, char **, int *, priv_set_t *); static int isnumber(char *); static void usage(void); extern char **environ; #define PROFLIST_SEP "," int main(int argc, char *argv[]) { char *cmd; char **cmdargs; char cmd_realpath[MAXPATHLEN]; int c; char *pset = NULL; (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); while ((c = getopt(argc, argv, "P:")) != EOF) { switch (c) { case 'P': if (pset == NULL) { pset = optarg; break; } /* FALLTHROUGH */ default: usage(); } } argc -= optind; argv += optind; if (argc < 1) usage(); cmd = argv[0]; cmdargs = &argv[0]; if (pset != NULL) { uid_t uid = getuid(); priv_set_t *wanted = get_privset(pset); priv_set_t *granted; adt_session_data_t *ah; /* audit session handle */ adt_event_data_t *event; /* event to be generated */ char cwd[MAXPATHLEN]; granted = get_granted_privs(uid); /* Audit use */ if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) { perror("pfexec: adt_start_session"); exit(EXIT_FAILURE); } if ((event = adt_alloc_event(ah, ADT_prof_cmd)) == NULL) { perror("pfexec: adt_alloc_event"); exit(EXIT_FAILURE); } if ((event->adt_prof_cmd.cwdpath = getcwd(cwd, sizeof (cwd))) == NULL) { (void) fprintf(stderr, gettext("pfexec: can't add cwd path\n")); exit(EXIT_FAILURE); } event->adt_prof_cmd.cmdpath = cmd; event->adt_prof_cmd.argc = argc - 1; event->adt_prof_cmd.argv = &argv[1]; event->adt_prof_cmd.envp = environ; if (granted != NULL) { priv_intersect(granted, wanted); event->adt_prof_cmd.inherit_set = wanted; if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS) != 0) { perror("pfexec: adt_put_event"); exit(EXIT_FAILURE); } if (setppriv(PRIV_ON, PRIV_INHERITABLE, wanted) != 0) { (void) fprintf(stderr, gettext("setppriv(): %s\n"), strerror(errno)); exit(EXIT_FAILURE); } /* Trick exec into thinking we're not suid */ (void) setppriv(PRIV_ON, PRIV_PERMITTED, wanted); priv_freeset(event->adt_prof_cmd.inherit_set); } else { if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS) != 0) { perror("pfexec: adt_put_event"); exit(EXIT_FAILURE); } } adt_free_event(event); (void) adt_end_session(ah); (void) setreuid(uid, uid); (void) execvp(cmd, cmdargs); (void) fprintf(stderr, gettext("pfexec: can't execute %s: %s\n"), cmd, strerror(errno)); exit(EXIT_FAILURE); } if ((cmd = pathsearch(cmd)) == NULL) exit(EXIT_FAILURE); if (getrealpath(cmd, cmd_realpath) == 0) exit(EXIT_FAILURE); if (checkattrs(cmd_realpath, argc, argv) == 0) exit(EXIT_FAILURE); (void) execv(cmd, cmdargs); /* * We'd be here only if execv fails. */ (void) fprintf(stderr, gettext("pfexec: can't execute %s: %s\n"), cmd, strerror(errno)); exit(EXIT_FAILURE); /* LINTED */ } /* * gets realpath for cmd. * return 1 on success, 0 on failure. */ static int getrealpath(const char *cmd, char *cmd_realpath) { if (realpath(cmd, cmd_realpath) == NULL) { (void) fprintf(stderr, gettext("pfexec: can't get real path of ``%s''\n"), cmd); return (0); } return (1); } /* * gets execution attributed for cmd, sets uids/gids, checks environ. * returns 1 on success, 0 on failure. */ static int checkattrs(char *cmd_realpath, int argc, char *argv[]) { char *value; uid_t uid, euid; gid_t gid = (gid_t)-1; gid_t egid = (gid_t)-1; struct passwd *pwent; execattr_t *exec; priv_set_t *lset = NULL; priv_set_t *iset = NULL; adt_session_data_t *ah; /* audit session handle */ adt_event_data_t *event; /* event to be generated */ char cwd[MAXPATHLEN]; uid = euid = getuid(); if ((pwent = getpwuid(uid)) == NULL) { (void) fprintf(stderr, "%d: ", (int)uid); (void) fprintf(stderr, gettext("can't get passwd entry\n")); return (0); } /* Set up to audit use */ if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) { perror("pfexec: adt_start_session"); return (0); } if ((event = adt_alloc_event(ah, ADT_prof_cmd)) == NULL) { perror("pfexec: adt_alloc_event"); return (0); } if ((event->adt_prof_cmd.cwdpath = getcwd(cwd, sizeof (cwd))) == NULL) { (void) fprintf(stderr, gettext("pfexec: can't add cwd path\n")); return (0); } /* * Get the exec attrs: uid, gid, euid and egid */ if ((exec = getexecuser(pwent->pw_name, KV_COMMAND, (char *)cmd_realpath, GET_ONE)) == NULL) { (void) fprintf(stderr, "%s: ", cmd_realpath); (void) fprintf(stderr, gettext("can't get execution attributes\n")); return (0); } if ((value = kva_match(exec->attr, EXECATTR_UID_KW)) != NULL) { euid = uid = get_uid(value); event->adt_prof_cmd.proc_euid = uid; event->adt_prof_cmd.proc_ruid = uid; } if ((value = kva_match(exec->attr, EXECATTR_GID_KW)) != NULL) { egid = gid = get_gid(value); event->adt_prof_cmd.proc_egid = gid; event->adt_prof_cmd.proc_rgid = gid; } if ((value = kva_match(exec->attr, EXECATTR_EUID_KW)) != NULL) { event->adt_prof_cmd.proc_euid = euid = get_uid(value); } if ((value = kva_match(exec->attr, EXECATTR_EGID_KW)) != NULL) { event->adt_prof_cmd.proc_egid = egid = get_gid(value); } if ((value = kva_match(exec->attr, EXECATTR_LPRIV_KW)) != NULL) { lset = get_privset(value); event->adt_prof_cmd.limit_set = lset; } if ((value = kva_match(exec->attr, EXECATTR_IPRIV_KW)) != NULL) { iset = get_privset(value); event->adt_prof_cmd.inherit_set = iset; } if (euid == uid || iset != NULL) { sanitize_environ(); } /* Finish audit info */ event->adt_prof_cmd.cmdpath = cmd_realpath; event->adt_prof_cmd.argc = argc - 1; event->adt_prof_cmd.argv = &argv[1]; event->adt_prof_cmd.envp = environ; if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS) != 0) { perror("pfexec: adt_put_event"); return (0); } adt_free_event(event); (void) adt_end_session(ah); set_attrs: /* * Set gids/uids and privileges. * */ if ((gid != (gid_t)-1) || (egid != (gid_t)-1)) { if ((setregid(gid, egid) == -1)) { (void) fprintf(stderr, "%s: ", cmd_realpath); (void) fprintf(stderr, gettext("can't set gid\n")); return (0); } } if (lset != NULL && setppriv(PRIV_SET, PRIV_LIMIT, lset) != 0 || iset != NULL && setppriv(PRIV_ON, PRIV_INHERITABLE, iset) != 0) { (void) fprintf(stderr, gettext("%s: can't set privileges\n"), cmd_realpath); return (0); } if (setreuid(uid, euid) == -1) { (void) fprintf(stderr, "%s: ", cmd_realpath); (void) fprintf(stderr, gettext("can't set uid\n")); return (0); } if (iset != NULL && getppriv(PRIV_INHERITABLE, iset) == 0) (void) setppriv(PRIV_SET, PRIV_PERMITTED, iset); free_execattr(exec); return (1); } /* * cleans up environ. code from su.c */ static void sanitize_environ() { char **pp = environ; char **qq, *p; while ((p = *pp) != NULL) { if (*p == 'L' && p[1] == 'D' && p[2] == '_') { for (qq = pp; (*qq = qq[1]) != NULL; qq++) { ; } } else { pp++; } } } static uid_t get_uid(char *value) { struct passwd *passwd_ent; if ((passwd_ent = getpwnam(value)) != NULL) return (passwd_ent->pw_uid); if (isnumber(value)) return (atoi(value)); (void) fprintf(stderr, "pfexec: %s: ", value); (void) fprintf(stderr, gettext("can't get user entry\n")); exit(EXIT_FAILURE); /*NOTREACHED*/ } static uid_t get_gid(char *value) { struct group *group_ent; if ((group_ent = getgrnam(value)) != NULL) return (group_ent->gr_gid); if (isnumber(value)) return (atoi(value)); (void) fprintf(stderr, "pfexec: %s: ", value); (void) fprintf(stderr, gettext("can't get group entry\n")); exit(EXIT_FAILURE); /*NOTREACHED*/ } static int isnumber(char *s) { int c; if (*s == '\0') return (0); while ((c = *s++) != '\0') { if (!isdigit(c)) { return (0); } } return (1); } static priv_set_t * get_privset(const char *s) { priv_set_t *res; if ((res = priv_str_to_set(s, ",", NULL)) == NULL) { (void) fprintf(stderr, "%s: bad privilege set\n", s); exit(EXIT_FAILURE); } return (res); } static void usage(void) { (void) fprintf(stderr, gettext("pfexec [-P privset] cmd [arg ..]\n")); exit(EXIT_FAILURE); } /* * This routine exists on failure and returns NULL if no granted privileges * are set. */ static priv_set_t * get_granted_privs(uid_t uid) { struct passwd *pwent; userattr_t *ua; char *profs; priv_set_t *res; char *profArray[MAXPROFS]; int profcnt = 0; res = priv_allocset(); if (res == NULL) { perror("priv_allocset"); exit(EXIT_FAILURE); } priv_emptyset(res); if ((pwent = getpwuid(uid)) == NULL) { (void) fprintf(stderr, "%d: ", (int)uid); (void) fprintf(stderr, gettext("can't get passwd entry\n")); exit(EXIT_FAILURE); } ua = getusernam(pwent->pw_name); if (ua != NULL && ua->attr != NULL && (profs = kva_match(ua->attr, USERATTR_PROFILES_KW)) != NULL) { get_profile_privs(profs, profArray, &profcnt, res); free_proflist(profArray, profcnt); } get_default_privs(pwent->pw_name, res); if (ua != NULL) free_userattr(ua); return (res); } static void get_default_privs(const char *user, priv_set_t *pset) { char *profs = NULL; char *profArray[MAXPROFS]; int profcnt = 0; if (_get_user_defs(user, NULL, &profs) == 0) { /* get privileges from default profiles */ if (profs != NULL) { get_profile_privs(profs, profArray, &profcnt, pset); free_proflist(profArray, profcnt); _free_user_defs(NULL, profs); } } } static void get_profile_privs(char *profiles, char **profArray, int *profcnt, priv_set_t *pset) { char *prof; char *lasts; profattr_t *pa; char *privs; int i; for (prof = strtok_r(profiles, PROFLIST_SEP, &lasts); prof != NULL; prof = strtok_r(NULL, PROFLIST_SEP, &lasts)) getproflist(prof, profArray, profcnt); /* get the privileges from list of profiles */ for (i = 0; i < *profcnt; i++) { if ((pa = getprofnam(profArray[i])) == NULL) { /* * this should never happen. * unless the database has an undefined profile */ continue; } /* get privs from this profile */ privs = kva_match(pa->attr, PROFATTR_PRIVS_KW); if (privs != NULL) { priv_set_t *tmp = priv_str_to_set(privs, ",", NULL); if (tmp != NULL) { priv_union(tmp, pset); priv_freeset(tmp); } } free_profattr(pa); } } /* * True if someone (user, group, other) can execute this file. */ #define S_ISEXEC(mode) (((mode)&(S_IXUSR|S_IXGRP|S_IXOTH)) != 0) /* * This function can return either the first argument or dynamically * allocated memory. Reuse with care. */ static char * pathsearch(char *cmd) { char *path, *dir, *result; char buf[MAXPATHLEN]; struct stat stbuf; /* * Implement shell like PATH searching; if the pathname contains * one or more slashes, don't search the path, even if the '/' * isn't the first character. (E.g., ./command or dir/command) * No path equals to a search in ".", just like the shell. */ if (strchr(cmd, '/') != NULL) return (cmd); path = getenv("PATH"); if (path == NULL) return (cmd); /* * We need to copy $PATH because our sub processes may need it. */ path = strdup(path); if (path == NULL) { perror("pfexec: strdup $PATH"); exit(EXIT_FAILURE); } result = NULL; for (dir = strtok(path, ":"); dir; dir = strtok(NULL, ":")) { if (snprintf(buf, sizeof (buf), "%s/%s", dir, cmd) >= sizeof (buf)) { continue; } if (stat(buf, &stbuf) < 0) continue; /* * Shells typically call access() with E_OK flag * to determine if the effective uid can execute * the file. We don't know what the eventual euid * will be; it is determined by the exec_attr * attributes which depend on the full pathname of * the command. Therefore, we match the first regular * file we find that is executable by someone. */ if (S_ISREG(stbuf.st_mode) && S_ISEXEC(stbuf.st_mode)) { result = strdup(buf); break; } } free(path); if (result == NULL) (void) fprintf(stderr, gettext("%s: Command not found\n"), cmd); return (result); }