1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2007 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 #include <errno.h> 29 #include <deflt.h> 30 #include <locale.h> 31 #include <sys/types.h> 32 #include <sys/param.h> 33 #include <stdlib.h> 34 #include <unistd.h> 35 #include <ctype.h> 36 #include <pwd.h> 37 #include <grp.h> 38 #include <string.h> 39 #include <exec_attr.h> 40 #include <user_attr.h> 41 #include <auth_attr.h> 42 #include <prof_attr.h> 43 #include <errno.h> 44 #include <priv.h> 45 46 #include <bsm/adt.h> 47 #include <bsm/adt_event.h> 48 49 #ifndef TEXT_DOMAIN /* Should be defined by cc -D */ 50 #define TEXT_DOMAIN "SYS_TEST" 51 #endif 52 53 extern int cannot_audit(int); 54 55 static char *pathsearch(char *); 56 static int getrealpath(const char *, char *); 57 static int checkattrs(char *, int, char *[]); 58 static void sanitize_environ(); 59 static uid_t get_uid(char *); 60 static gid_t get_gid(char *); 61 static priv_set_t *get_privset(const char *); 62 static priv_set_t *get_granted_privs(uid_t); 63 static void get_default_privs(priv_set_t *); 64 static void get_profile_privs(char *, char **, int *, priv_set_t *); 65 66 static int isnumber(char *); 67 static void usage(void); 68 69 extern char **environ; 70 71 #define PROFLIST_SEP "," 72 73 int 74 main(int argc, char *argv[]) 75 { 76 char *cmd; 77 char **cmdargs; 78 char cmd_realpath[MAXPATHLEN]; 79 int c; 80 char *pset = NULL; 81 82 (void) setlocale(LC_ALL, ""); 83 (void) textdomain(TEXT_DOMAIN); 84 85 while ((c = getopt(argc, argv, "P:")) != EOF) { 86 switch (c) { 87 case 'P': 88 if (pset == NULL) { 89 pset = optarg; 90 break; 91 } 92 /* FALLTHROUGH */ 93 default: 94 usage(); 95 } 96 } 97 argc -= optind; 98 argv += optind; 99 100 if (argc < 1) 101 usage(); 102 103 cmd = argv[0]; 104 cmdargs = &argv[0]; 105 106 if (pset != NULL) { 107 uid_t uid = getuid(); 108 priv_set_t *wanted = get_privset(pset); 109 priv_set_t *granted; 110 111 adt_session_data_t *ah; /* audit session handle */ 112 adt_event_data_t *event; /* event to be generated */ 113 char cwd[MAXPATHLEN]; 114 115 granted = get_granted_privs(uid); 116 117 /* Audit use */ 118 if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) { 119 perror("pfexec: adt_start_session"); 120 exit(EXIT_FAILURE); 121 } 122 if ((event = adt_alloc_event(ah, ADT_prof_cmd)) == NULL) { 123 perror("pfexec: adt_alloc_event"); 124 exit(EXIT_FAILURE); 125 } 126 if ((event->adt_prof_cmd.cwdpath = 127 getcwd(cwd, sizeof (cwd))) == NULL) { 128 (void) fprintf(stderr, 129 gettext("pfexec: can't add cwd path\n")); 130 exit(EXIT_FAILURE); 131 } 132 133 event->adt_prof_cmd.cmdpath = cmd; 134 event->adt_prof_cmd.argc = argc - 1; 135 event->adt_prof_cmd.argv = &argv[1]; 136 event->adt_prof_cmd.envp = environ; 137 138 if (granted != NULL) { 139 priv_intersect(granted, wanted); 140 event->adt_prof_cmd.inherit_set = wanted; 141 if (adt_put_event(event, ADT_SUCCESS, 142 ADT_SUCCESS) != 0) { 143 perror("pfexec: adt_put_event"); 144 exit(EXIT_FAILURE); 145 } 146 if (setppriv(PRIV_ON, PRIV_INHERITABLE, wanted) != 0) { 147 (void) fprintf(stderr, 148 gettext("setppriv(): %s\n"), 149 strerror(errno)); 150 exit(EXIT_FAILURE); 151 } 152 /* Trick exec into thinking we're not suid */ 153 (void) setppriv(PRIV_ON, PRIV_PERMITTED, wanted); 154 priv_freeset(event->adt_prof_cmd.inherit_set); 155 } else { 156 if (adt_put_event(event, ADT_SUCCESS, 157 ADT_SUCCESS) != 0) { 158 perror("pfexec: adt_put_event"); 159 exit(EXIT_FAILURE); 160 } 161 } 162 adt_free_event(event); 163 (void) adt_end_session(ah); 164 (void) setreuid(uid, uid); 165 (void) execvp(cmd, cmdargs); 166 perror(cmd); 167 exit(EXIT_FAILURE); 168 } 169 170 if ((cmd = pathsearch(cmd)) == NULL) 171 exit(EXIT_FAILURE); 172 173 if (getrealpath(cmd, cmd_realpath) == 0) 174 exit(EXIT_FAILURE); 175 176 if (checkattrs(cmd_realpath, argc, argv) == 0) 177 exit(EXIT_FAILURE); 178 179 (void) execv(cmd, cmdargs); 180 /* 181 * We'd be here only if execv fails. 182 */ 183 perror("pfexec"); 184 exit(EXIT_FAILURE); 185 /* LINTED */ 186 } 187 188 189 /* 190 * gets realpath for cmd. 191 * return 1 on success, 0 on failure. 192 */ 193 static int 194 getrealpath(const char *cmd, char *cmd_realpath) 195 { 196 if (realpath(cmd, cmd_realpath) == NULL) { 197 (void) fprintf(stderr, 198 gettext("pfexec: can't get real path of ``%s''\n"), cmd); 199 return (0); 200 } 201 return (1); 202 } 203 204 /* 205 * gets execution attributed for cmd, sets uids/gids, checks environ. 206 * returns 1 on success, 0 on failure. 207 */ 208 static int 209 checkattrs(char *cmd_realpath, int argc, char *argv[]) 210 { 211 char *value; 212 uid_t uid, euid; 213 gid_t gid = (gid_t)-1; 214 gid_t egid = (gid_t)-1; 215 struct passwd *pwent; 216 execattr_t *exec; 217 priv_set_t *lset = NULL; 218 priv_set_t *iset = NULL; 219 220 adt_session_data_t *ah; /* audit session handle */ 221 adt_event_data_t *event; /* event to be generated */ 222 char cwd[MAXPATHLEN]; 223 224 uid = euid = getuid(); 225 if ((pwent = getpwuid(uid)) == NULL) { 226 (void) fprintf(stderr, "%d: ", (int)uid); 227 (void) fprintf(stderr, gettext("can't get passwd entry\n")); 228 return (0); 229 } 230 /* Set up to audit use */ 231 if (adt_start_session(&ah, NULL, ADT_USE_PROC_DATA) != 0) { 232 perror("pfexec: adt_start_session"); 233 return (0); 234 } 235 if ((event = adt_alloc_event(ah, ADT_prof_cmd)) == NULL) { 236 perror("pfexec: adt_alloc_event"); 237 return (0); 238 } 239 if ((event->adt_prof_cmd.cwdpath = getcwd(cwd, sizeof (cwd))) == NULL) { 240 (void) fprintf(stderr, gettext("pfexec: can't add cwd path\n")); 241 return (0); 242 } 243 /* 244 * Get the exec attrs: uid, gid, euid and egid 245 */ 246 if ((exec = getexecuser(pwent->pw_name, 247 KV_COMMAND, (char *)cmd_realpath, GET_ONE)) == NULL) { 248 (void) fprintf(stderr, "%s: ", cmd_realpath); 249 (void) fprintf(stderr, 250 gettext("can't get execution attributes\n")); 251 return (0); 252 } 253 if ((value = kva_match(exec->attr, EXECATTR_UID_KW)) != NULL) { 254 euid = uid = get_uid(value); 255 event->adt_prof_cmd.proc_euid = uid; 256 event->adt_prof_cmd.proc_ruid = uid; 257 } 258 if ((value = kva_match(exec->attr, EXECATTR_GID_KW)) != NULL) { 259 egid = gid = get_gid(value); 260 event->adt_prof_cmd.proc_egid = gid; 261 event->adt_prof_cmd.proc_rgid = gid; 262 } 263 if ((value = kva_match(exec->attr, EXECATTR_EUID_KW)) != NULL) { 264 event->adt_prof_cmd.proc_euid = euid = get_uid(value); 265 } 266 if ((value = kva_match(exec->attr, EXECATTR_EGID_KW)) != NULL) { 267 event->adt_prof_cmd.proc_egid = egid = get_gid(value); 268 } 269 if ((value = kva_match(exec->attr, EXECATTR_LPRIV_KW)) != NULL) { 270 lset = get_privset(value); 271 event->adt_prof_cmd.limit_set = lset; 272 } 273 if ((value = kva_match(exec->attr, EXECATTR_IPRIV_KW)) != NULL) { 274 iset = get_privset(value); 275 event->adt_prof_cmd.inherit_set = iset; 276 } 277 if (euid == uid || iset != NULL) { 278 sanitize_environ(); 279 } 280 281 /* Finish audit info */ 282 event->adt_prof_cmd.cmdpath = cmd_realpath; 283 event->adt_prof_cmd.argc = argc - 1; 284 event->adt_prof_cmd.argv = &argv[1]; 285 event->adt_prof_cmd.envp = environ; 286 if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS) != 0) { 287 perror("pfexec: adt_put_event"); 288 return (0); 289 } 290 adt_free_event(event); 291 (void) adt_end_session(ah); 292 293 set_attrs: 294 /* 295 * Set gids/uids and privileges. 296 * 297 */ 298 if ((gid != (gid_t)-1) || (egid != (gid_t)-1)) { 299 if ((setregid(gid, egid) == -1)) { 300 (void) fprintf(stderr, "%s: ", cmd_realpath); 301 (void) fprintf(stderr, gettext("can't set gid\n")); 302 return (0); 303 } 304 } 305 if (lset != NULL && setppriv(PRIV_SET, PRIV_LIMIT, lset) != 0 || 306 iset != NULL && setppriv(PRIV_ON, PRIV_INHERITABLE, iset) != 0) { 307 (void) fprintf(stderr, gettext("%s: can't set privileges\n"), 308 cmd_realpath); 309 return (0); 310 } 311 if (setreuid(uid, euid) == -1) { 312 (void) fprintf(stderr, "%s: ", cmd_realpath); 313 (void) fprintf(stderr, gettext("can't set uid\n")); 314 return (0); 315 } 316 if (iset != NULL && getppriv(PRIV_INHERITABLE, iset) == 0) 317 (void) setppriv(PRIV_SET, PRIV_PERMITTED, iset); 318 319 free_execattr(exec); 320 321 return (1); 322 } 323 324 325 /* 326 * cleans up environ. code from su.c 327 */ 328 static void 329 sanitize_environ() 330 { 331 char **pp = environ; 332 char **qq, *p; 333 334 while ((p = *pp) != NULL) { 335 if (*p == 'L' && p[1] == 'D' && p[2] == '_') { 336 for (qq = pp; (*qq = qq[1]) != NULL; qq++) { 337 ; 338 } 339 } else { 340 pp++; 341 } 342 } 343 } 344 345 346 static uid_t 347 get_uid(char *value) 348 { 349 struct passwd *passwd_ent; 350 351 if ((passwd_ent = getpwnam(value)) != NULL) 352 return (passwd_ent->pw_uid); 353 354 if (isnumber(value)) 355 return (atoi(value)); 356 357 (void) fprintf(stderr, "pfexec: %s: ", value); 358 (void) fprintf(stderr, gettext("can't get user entry\n")); 359 exit(EXIT_FAILURE); 360 /*NOTREACHED*/ 361 } 362 363 364 static uid_t 365 get_gid(char *value) 366 { 367 struct group *group_ent; 368 369 if ((group_ent = getgrnam(value)) != NULL) 370 return (group_ent->gr_gid); 371 372 if (isnumber(value)) 373 return (atoi(value)); 374 375 (void) fprintf(stderr, "pfexec: %s: ", value); 376 (void) fprintf(stderr, gettext("can't get group entry\n")); 377 exit(EXIT_FAILURE); 378 /*NOTREACHED*/ 379 } 380 381 382 static int 383 isnumber(char *s) 384 { 385 int c; 386 387 if (*s == '\0') 388 return (0); 389 390 while ((c = *s++) != '\0') { 391 if (!isdigit(c)) { 392 return (0); 393 } 394 } 395 396 return (1); 397 } 398 399 static priv_set_t * 400 get_privset(const char *s) 401 { 402 priv_set_t *res; 403 404 if ((res = priv_str_to_set(s, ",", NULL)) == NULL) { 405 (void) fprintf(stderr, "%s: bad privilege set\n", s); 406 exit(EXIT_FAILURE); 407 } 408 return (res); 409 } 410 411 static void 412 usage(void) 413 { 414 (void) fprintf(stderr, gettext("pfexec [-P privset] cmd [arg ..]\n")); 415 exit(EXIT_FAILURE); 416 } 417 418 419 /* 420 * This routine exists on failure and returns NULL if no granted privileges 421 * are set. 422 */ 423 static priv_set_t * 424 get_granted_privs(uid_t uid) 425 { 426 struct passwd *pwent; 427 userattr_t *ua; 428 char *profs; 429 priv_set_t *res; 430 char *profArray[MAXPROFS]; 431 int profcnt = 0; 432 433 res = priv_allocset(); 434 if (res == NULL) { 435 perror("priv_allocset"); 436 exit(EXIT_FAILURE); 437 } 438 439 priv_emptyset(res); 440 441 if ((pwent = getpwuid(uid)) == NULL) { 442 (void) fprintf(stderr, "%d: ", (int)uid); 443 (void) fprintf(stderr, gettext("can't get passwd entry\n")); 444 exit(EXIT_FAILURE); 445 } 446 447 ua = getusernam(pwent->pw_name); 448 449 if (ua != NULL && ua->attr != NULL && 450 (profs = kva_match(ua->attr, USERATTR_PROFILES_KW)) != NULL) { 451 get_profile_privs(profs, profArray, &profcnt, res); 452 free_proflist(profArray, profcnt); 453 } 454 455 get_default_privs(res); 456 457 if (ua != NULL) 458 free_userattr(ua); 459 460 return (res); 461 } 462 463 static void 464 get_default_privs(priv_set_t *pset) 465 { 466 char *profs = NULL; 467 char *profArray[MAXPROFS]; 468 int profcnt = 0; 469 470 if (defopen(AUTH_POLICY) == 0) { 471 /* get privileges from default profiles */ 472 profs = defread(DEF_PROF); 473 if (profs != NULL) { 474 get_profile_privs(profs, profArray, &profcnt, pset); 475 free_proflist(profArray, profcnt); 476 } 477 } 478 (void) defopen(NULL); 479 } 480 481 static void 482 get_profile_privs(char *profiles, char **profArray, int *profcnt, 483 priv_set_t *pset) 484 { 485 486 char *prof; 487 char *lasts; 488 profattr_t *pa; 489 char *privs; 490 int i; 491 492 for (prof = strtok_r(profiles, PROFLIST_SEP, &lasts); 493 prof != NULL; 494 prof = strtok_r(NULL, PROFLIST_SEP, &lasts)) 495 getproflist(prof, profArray, profcnt); 496 497 /* get the privileges from list of profiles */ 498 for (i = 0; i < *profcnt; i++) { 499 500 if ((pa = getprofnam(profArray[i])) == NULL) { 501 /* 502 * this should never happen. 503 * unless the database has an undefined profile 504 */ 505 continue; 506 } 507 508 /* get privs from this profile */ 509 privs = kva_match(pa->attr, PROFATTR_PRIVS_KW); 510 if (privs != NULL) { 511 priv_set_t *tmp = priv_str_to_set(privs, ",", NULL); 512 if (tmp != NULL) { 513 priv_union(tmp, pset); 514 priv_freeset(tmp); 515 } 516 } 517 518 free_profattr(pa); 519 } 520 } 521 522 /* 523 * This function can return either the first argument or dynamically 524 * allocated memory. Reuse with care. 525 */ 526 static char * 527 pathsearch(char *cmd) 528 { 529 char *path, *dir; 530 char buf[MAXPATHLEN]; 531 532 /* 533 * Implement shell like PATH searching; if the pathname contains 534 * one or more slashes, don't search the path, even if the '/' 535 * isn't the first character. (E.g., ./command or dir/command) 536 * No path equals to a search in ".", just like the shell. 537 */ 538 if (strchr(cmd, '/') != NULL) 539 return (cmd); 540 541 path = getenv("PATH"); 542 if (path == NULL) 543 return (cmd); 544 545 /* 546 * We need to copy $PATH because our sub processes may need it. 547 */ 548 path = strdup(path); 549 if (path == NULL) { 550 perror("pfexec: strdup $PATH"); 551 exit(EXIT_FAILURE); 552 } 553 554 for (dir = strtok(path, ":"); dir; dir = strtok(NULL, ":")) { 555 if (snprintf(buf, sizeof (buf), "%s/%s", dir, cmd) >= 556 sizeof (buf)) { 557 continue; 558 } 559 if (access(buf, X_OK) == 0) { 560 free(path); 561 return (strdup(buf)); 562 } 563 } 564 free(path); 565 (void) fprintf(stderr, gettext("%s: Command not found\n"), cmd); 566 return (NULL); 567 } 568