1 /* Author: Mark Goldman <mgoldman (at) tresys.com> 2 * Paul Rosenfeld <prosenfeld (at) tresys.com> 3 * Todd C. Miller <tmiller (at) tresys.com> 4 * 5 * Copyright (C) 2007 Tresys Technology, LLC 6 * 7 * This library is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU Lesser General Public License as 9 * published by the Free Software Foundation; either version 2.1 of the 10 * License, or (at your option) any later version. 11 * 12 * This library is distributed in the hope that it will be useful, but 13 * WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with this library; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 20 * 02110-1301 USA 21 */ 22 23 #include <semanage/handle.h> 24 #include <semanage/seusers_policy.h> 25 #include <semanage/users_policy.h> 26 #include <semanage/user_record.h> 27 #include <semanage/fcontext_record.h> 28 #include <semanage/fcontexts_policy.h> 29 #include <sepol/context.h> 30 #include <sepol/context_record.h> 31 #include "semanage_store.h" 32 #include "seuser_internal.h" 33 #include "debug.h" 34 35 #include "utilities.h" 36 #include "genhomedircon.h" 37 38 #include <assert.h> 39 #include <ctype.h> 40 #include <limits.h> 41 #include <stdio.h> 42 #include <stdlib.h> 43 #include <string.h> 44 #include <sys/types.h> 45 #include <sys/stat.h> 46 #include <fcntl.h> 47 #include <pwd.h> 48 #include <errno.h> 49 #include <unistd.h> 50 #include <regex.h> 51 #include <grp.h> 52 #include <search.h> 53 54 /* paths used in get_home_dirs() */ 55 #define PATH_ETC_USERADD "/etc/default/useradd" 56 #define PATH_ETC_LIBUSER "/etc/libuser.conf" 57 #define PATH_DEFAULT_HOME "/home" 58 #define PATH_EXPORT_HOME "/export/home" 59 #define PATH_ETC_LOGIN_DEFS "/etc/login.defs" 60 61 /* other paths */ 62 #define PATH_SHELLS_FILE "/etc/shells" 63 #define PATH_NOLOGIN_SHELL "/sbin/nologin" 64 65 /* comments written to context file */ 66 #define COMMENT_FILE_CONTEXT_HEADER "#\n#\n# " \ 67 "User-specific file contexts, generated via libsemanage\n" \ 68 "# use semanage command to manage system users to change" \ 69 " the file_context\n#\n#\n" 70 71 #define COMMENT_USER_HOME_CONTEXT "\n\n#\n# Home Context for user %s" \ 72 "\n#\n\n" 73 74 /* placeholders used in the template file 75 which are searched for and replaced */ 76 #define TEMPLATE_HOME_ROOT "HOME_ROOT" 77 #define TEMPLATE_HOME_DIR "HOME_DIR" 78 /* these are legacy */ 79 #define TEMPLATE_USER "USER" 80 #define TEMPLATE_ROLE "ROLE" 81 /* new names */ 82 #define TEMPLATE_USERNAME "%{USERNAME}" 83 #define TEMPLATE_USERID "%{USERID}" 84 85 #define FALLBACK_SENAME "user_u" 86 #define FALLBACK_PREFIX "user" 87 #define FALLBACK_LEVEL "s0" 88 #define FALLBACK_NAME "[^/]+" 89 #define FALLBACK_UIDGID "[0-9]+" 90 #define DEFAULT_LOGIN "__default__" 91 92 #define CONTEXT_NONE "<<none>>" 93 94 typedef struct user_entry { 95 char *name; 96 char *uid; 97 char *gid; 98 char *sename; 99 char *prefix; 100 char *home; 101 char *level; 102 char *login; 103 char *homedir_role; 104 struct user_entry *next; 105 } genhomedircon_user_entry_t; 106 107 typedef struct { 108 const char *fcfilepath; 109 int usepasswd; 110 const char *homedir_template_path; 111 genhomedircon_user_entry_t *fallback; 112 semanage_handle_t *h_semanage; 113 sepol_policydb_t *policydb; 114 } genhomedircon_settings_t; 115 116 typedef struct { 117 const char *search_for; 118 const char *replace_with; 119 } replacement_pair_t; 120 121 typedef struct { 122 const char *dir; 123 int matched; 124 } fc_match_handle_t; 125 126 typedef struct IgnoreDir { 127 struct IgnoreDir *next; 128 char *dir; 129 } ignoredir_t; 130 131 ignoredir_t *ignore_head = NULL; 132 133 static void ignore_free(void) { 134 ignoredir_t *next; 135 136 while (ignore_head) { 137 next = ignore_head->next; 138 free(ignore_head->dir); 139 free(ignore_head); 140 ignore_head = next; 141 } 142 } 143 144 static int ignore_setup(char *ignoredirs) { 145 char *tok; 146 ignoredir_t *ptr = NULL; 147 148 tok = strtok(ignoredirs, ";"); 149 while(tok) { 150 ptr = calloc(sizeof(ignoredir_t),1); 151 if (!ptr) 152 goto err; 153 ptr->dir = strdup(tok); 154 if (!ptr->dir) 155 goto err; 156 157 ptr->next = ignore_head; 158 ignore_head = ptr; 159 160 tok = strtok(NULL, ";"); 161 } 162 163 return 0; 164 err: 165 free(ptr); 166 ignore_free(); 167 return -1; 168 } 169 170 static int ignore(const char *homedir) { 171 ignoredir_t *ptr = ignore_head; 172 while (ptr) { 173 if (strcmp(ptr->dir, homedir) == 0) { 174 return 1; 175 } 176 ptr = ptr->next; 177 } 178 return 0; 179 } 180 181 static int prefix_is_homedir_role(const semanage_user_t *user, 182 const char *prefix) 183 { 184 return strcmp(OBJECT_R, prefix) == 0 || 185 semanage_user_has_role(user, prefix); 186 } 187 188 static semanage_list_t *default_shell_list(void) 189 { 190 semanage_list_t *list = NULL; 191 192 if (semanage_list_push(&list, "/bin/csh") 193 || semanage_list_push(&list, "/bin/tcsh") 194 || semanage_list_push(&list, "/bin/ksh") 195 || semanage_list_push(&list, "/bin/bsh") 196 || semanage_list_push(&list, "/bin/ash") 197 || semanage_list_push(&list, "/usr/bin/ksh") 198 || semanage_list_push(&list, "/usr/bin/pdksh") 199 || semanage_list_push(&list, "/bin/zsh") 200 || semanage_list_push(&list, "/bin/sh") 201 || semanage_list_push(&list, "/bin/bash")) 202 goto fail; 203 204 return list; 205 206 fail: 207 semanage_list_destroy(&list); 208 return NULL; 209 } 210 211 static semanage_list_t *get_shell_list(void) 212 { 213 FILE *shells; 214 char *temp = NULL; 215 semanage_list_t *list = NULL; 216 size_t buff_len = 0; 217 ssize_t len; 218 219 shells = fopen(PATH_SHELLS_FILE, "r"); 220 if (!shells) 221 return default_shell_list(); 222 while ((len = getline(&temp, &buff_len, shells)) > 0) { 223 if (temp[len-1] == '\n') temp[len-1] = 0; 224 if (strcmp(temp, PATH_NOLOGIN_SHELL)) { 225 if (semanage_list_push(&list, temp)) { 226 free(temp); 227 semanage_list_destroy(&list); 228 return default_shell_list(); 229 } 230 } 231 } 232 free(temp); 233 234 return list; 235 } 236 237 /* Helper function called via semanage_fcontext_iterate() */ 238 static int fcontext_matches(const semanage_fcontext_t *fcontext, void *varg) 239 { 240 const char *oexpr = semanage_fcontext_get_expr(fcontext); 241 fc_match_handle_t *handp = varg; 242 char *expr = NULL; 243 regex_t re; 244 int type, retval = -1; 245 size_t len; 246 247 /* Only match ALL or DIR */ 248 type = semanage_fcontext_get_type(fcontext); 249 if (type != SEMANAGE_FCONTEXT_ALL && type != SEMANAGE_FCONTEXT_DIR) 250 return 0; 251 252 len = strlen(oexpr); 253 /* Define a macro to strip a literal string from the end of oexpr */ 254 #define rstrip_oexpr_len(cstr, cstrlen) \ 255 do { \ 256 if (len >= (cstrlen) && !strncmp(oexpr + len - (cstrlen), (cstr), (cstrlen))) \ 257 len -= (cstrlen); \ 258 } while (0) 259 #define rstrip_oexpr(cstr) rstrip_oexpr_len(cstr, sizeof(cstr) - 1) 260 261 rstrip_oexpr(".+"); 262 rstrip_oexpr(".*"); 263 rstrip_oexpr("(/.*)?"); 264 rstrip_oexpr("/"); 265 266 #undef rstrip_oexpr_len 267 #undef rstrip_oexpr 268 269 /* Anchor oexpr at the beginning and append pattern to eat up trailing slashes */ 270 if (asprintf(&expr, "^%.*s/*$", (int)len, oexpr) < 0) 271 return -1; 272 273 /* Check dir against expr */ 274 if (regcomp(&re, expr, REG_EXTENDED) != 0) 275 goto done; 276 if (regexec(&re, handp->dir, 0, NULL, 0) == 0) 277 handp->matched = 1; 278 regfree(&re); 279 280 retval = 0; 281 282 done: 283 free(expr); 284 285 return retval; 286 } 287 288 static semanage_list_t *get_home_dirs(genhomedircon_settings_t * s) 289 { 290 semanage_list_t *homedir_list = NULL; 291 semanage_list_t *shells = NULL; 292 fc_match_handle_t hand; 293 char *path = NULL; 294 uid_t temp, minuid = 500, maxuid = 60000; 295 int minuid_set = 0; 296 struct passwd *pwbuf; 297 struct stat buf; 298 299 path = semanage_findval(PATH_ETC_USERADD, "HOME", "="); 300 if (path && *path) { 301 if (semanage_list_push(&homedir_list, path)) 302 goto fail; 303 } 304 free(path); 305 306 path = semanage_findval(PATH_ETC_LIBUSER, "LU_HOMEDIRECTORY", "="); 307 if (path && *path) { 308 if (semanage_list_push(&homedir_list, path)) 309 goto fail; 310 } 311 free(path); 312 path = NULL; 313 314 if (!homedir_list) { 315 if (semanage_list_push(&homedir_list, PATH_DEFAULT_HOME)) { 316 goto fail; 317 } 318 } 319 320 if (!stat(PATH_EXPORT_HOME, &buf)) { 321 if (S_ISDIR(buf.st_mode)) { 322 if (semanage_list_push(&homedir_list, PATH_EXPORT_HOME)) { 323 goto fail; 324 } 325 } 326 } 327 328 if (!(s->usepasswd)) 329 return homedir_list; 330 331 shells = get_shell_list(); 332 assert(shells); 333 334 path = semanage_findval(PATH_ETC_LOGIN_DEFS, "UID_MIN", NULL); 335 if (path && *path) { 336 temp = atoi(path); 337 minuid = temp; 338 minuid_set = 1; 339 } 340 free(path); 341 path = NULL; 342 343 path = semanage_findval(PATH_ETC_LOGIN_DEFS, "UID_MAX", NULL); 344 if (path && *path) { 345 temp = atoi(path); 346 maxuid = temp; 347 } 348 free(path); 349 path = NULL; 350 351 path = semanage_findval(PATH_ETC_LIBUSER, "LU_UIDNUMBER", "="); 352 if (path && *path) { 353 temp = atoi(path); 354 if (!minuid_set || temp < minuid) { 355 minuid = temp; 356 minuid_set = 1; 357 } 358 } 359 free(path); 360 path = NULL; 361 362 errno = 0; 363 setpwent(); 364 while (1) { 365 errno = 0; 366 pwbuf = getpwent(); 367 if (pwbuf == NULL) 368 break; 369 if (pwbuf->pw_uid < minuid || pwbuf->pw_uid > maxuid) 370 continue; 371 if (!semanage_list_find(shells, pwbuf->pw_shell)) 372 continue; 373 int len = strlen(pwbuf->pw_dir) -1; 374 for(; len > 0 && pwbuf->pw_dir[len] == '/'; len--) { 375 pwbuf->pw_dir[len] = '\0'; 376 } 377 if (strcmp(pwbuf->pw_dir, "/") == 0) 378 continue; 379 if (ignore(pwbuf->pw_dir)) 380 continue; 381 if (semanage_str_count(pwbuf->pw_dir, '/') <= 1) 382 continue; 383 if (!(path = strdup(pwbuf->pw_dir))) { 384 break; 385 } 386 387 semanage_rtrim(path, '/'); 388 389 if (!semanage_list_find(homedir_list, path)) { 390 /* 391 * Now check for an existing file context that matches 392 * so we don't label a non-homedir as a homedir. 393 */ 394 hand.dir = path; 395 hand.matched = 0; 396 if (semanage_fcontext_iterate(s->h_semanage, 397 fcontext_matches, &hand) == STATUS_ERR) 398 goto fail; 399 400 /* NOTE: old genhomedircon printed a warning on match */ 401 if (hand.matched) { 402 WARN(s->h_semanage, "%s homedir %s or its parent directory conflicts with a file context already specified in the policy. This usually indicates an incorrectly defined system account. If it is a system account please make sure its uid is less than %u or greater than %u or its login shell is /sbin/nologin.", pwbuf->pw_name, pwbuf->pw_dir, minuid, maxuid); 403 } else { 404 if (semanage_list_push(&homedir_list, path)) 405 goto fail; 406 } 407 } 408 free(path); 409 path = NULL; 410 } 411 412 if (errno) { 413 WARN(s->h_semanage, "Error while fetching users. " 414 "Returning list so far."); 415 } 416 417 if (semanage_list_sort(&homedir_list)) 418 goto fail; 419 420 endpwent(); 421 semanage_list_destroy(&shells); 422 423 return homedir_list; 424 425 fail: 426 endpwent(); 427 free(path); 428 semanage_list_destroy(&homedir_list); 429 semanage_list_destroy(&shells); 430 return NULL; 431 } 432 433 /** 434 * @param out the FILE to put all the output in. 435 * @return 0 on success 436 */ 437 static int write_file_context_header(FILE * out) 438 { 439 if (fprintf(out, COMMENT_FILE_CONTEXT_HEADER) < 0) { 440 return STATUS_ERR; 441 } 442 443 return STATUS_SUCCESS; 444 } 445 446 /* Predicates for use with semanage_slurp_file_filter() the homedir_template 447 * file currently contains lines that serve as the template for a user's 448 * homedir. 449 * 450 * It also contains lines that are the template for the parent of a 451 * user's home directory. 452 * 453 * Currently, the only lines that apply to the the root of a user's home 454 * directory are all prefixed with the string "HOME_ROOT". All other 455 * lines apply to a user's home directory. If this changes the 456 * following predicates need to change to reflect that. 457 */ 458 static int HOME_ROOT_PRED(const char *string) 459 { 460 return semanage_is_prefix(string, TEMPLATE_HOME_ROOT); 461 } 462 463 static int HOME_DIR_PRED(const char *string) 464 { 465 return semanage_is_prefix(string, TEMPLATE_HOME_DIR); 466 } 467 468 /* new names */ 469 static int USERNAME_CONTEXT_PRED(const char *string) 470 { 471 return (int)( 472 (strstr(string, TEMPLATE_USERNAME) != NULL) || 473 (strstr(string, TEMPLATE_USERID) != NULL) 474 ); 475 } 476 477 /* This will never match USER if USERNAME or USERID are found. */ 478 static int USER_CONTEXT_PRED(const char *string) 479 { 480 if (USERNAME_CONTEXT_PRED(string)) 481 return 0; 482 483 return (int)(strstr(string, TEMPLATE_USER) != NULL); 484 } 485 486 static int STR_COMPARATOR(const void *a, const void *b) 487 { 488 return strcmp((const char *) a, (const char *) b); 489 } 490 491 /* make_tempate 492 * @param s the settings holding the paths to various files 493 * @param pred function pointer to function to use as filter for slurp 494 * file filter 495 * @return a list of lines from the template file with inappropriate 496 * lines filtered out. 497 */ 498 static semanage_list_t *make_template(genhomedircon_settings_t * s, 499 int (*pred) (const char *)) 500 { 501 FILE *template_file = NULL; 502 semanage_list_t *template_data = NULL; 503 504 template_file = fopen(s->homedir_template_path, "r"); 505 if (!template_file) 506 return NULL; 507 template_data = semanage_slurp_file_filter(template_file, pred); 508 fclose(template_file); 509 510 return template_data; 511 } 512 513 static char *replace_all(const char *str, const replacement_pair_t * repl) 514 { 515 char *retval, *retval2; 516 int i; 517 518 if (!str || !repl) 519 return NULL; 520 521 retval = strdup(str); 522 for (i = 0; retval != NULL && repl[i].search_for; i++) { 523 retval2 = semanage_str_replace(repl[i].search_for, 524 repl[i].replace_with, retval, 0); 525 free(retval); 526 retval = retval2; 527 } 528 return retval; 529 } 530 531 static const char *extract_context(const char *line) 532 { 533 const char *p = line; 534 size_t off; 535 536 off = strlen(p); 537 p += off; 538 /* consider trailing whitespaces */ 539 while (off > 0) { 540 p--; 541 off--; 542 if (!isspace(*p)) 543 break; 544 } 545 if (off == 0) 546 return NULL; 547 548 /* find the last field in line */ 549 while (off > 0 && !isspace(*(p - 1))) { 550 p--; 551 off--; 552 } 553 return p; 554 } 555 556 static int check_line(genhomedircon_settings_t * s, const char *line) 557 { 558 sepol_context_t *ctx_record = NULL; 559 const char *ctx_str; 560 int result; 561 562 ctx_str = extract_context(line); 563 if (!ctx_str) 564 return STATUS_ERR; 565 566 result = sepol_context_from_string(s->h_semanage->sepolh, 567 ctx_str, &ctx_record); 568 if (result == STATUS_SUCCESS && ctx_record != NULL) { 569 result = sepol_context_check(s->h_semanage->sepolh, 570 s->policydb, ctx_record); 571 sepol_context_free(ctx_record); 572 } 573 return result; 574 } 575 576 static int write_replacements(genhomedircon_settings_t * s, FILE * out, 577 const semanage_list_t * tpl, 578 const replacement_pair_t *repl) 579 { 580 char *line; 581 582 for (; tpl; tpl = tpl->next) { 583 line = replace_all(tpl->data, repl); 584 if (!line) 585 goto fail; 586 if (check_line(s, line) == STATUS_SUCCESS) { 587 if (fprintf(out, "%s\n", line) < 0) 588 goto fail; 589 } 590 free(line); 591 } 592 return STATUS_SUCCESS; 593 594 fail: 595 free(line); 596 return STATUS_ERR; 597 } 598 599 static int write_contexts(genhomedircon_settings_t *s, FILE *out, 600 semanage_list_t *tpl, const replacement_pair_t *repl, 601 const genhomedircon_user_entry_t *user) 602 { 603 char *line, *temp; 604 sepol_context_t *context; 605 char *new_context_str; 606 607 for (; tpl; tpl = tpl->next) { 608 context = NULL; 609 new_context_str = NULL; 610 line = replace_all(tpl->data, repl); 611 if (!line) { 612 goto fail; 613 } 614 615 const char *old_context_str = extract_context(line); 616 if (!old_context_str) { 617 goto fail; 618 } 619 620 if (strcmp(old_context_str, CONTEXT_NONE) == 0) { 621 if (check_line(s, line) == STATUS_SUCCESS && 622 fprintf(out, "%s\n", line) < 0) { 623 goto fail; 624 } 625 free(line); 626 continue; 627 } 628 629 sepol_handle_t *sepolh = s->h_semanage->sepolh; 630 631 if (sepol_context_from_string(sepolh, old_context_str, 632 &context) < 0) { 633 goto fail; 634 } 635 636 if (sepol_context_set_user(sepolh, context, user->sename) < 0) { 637 goto fail; 638 } 639 640 if (sepol_policydb_mls_enabled(s->policydb) && 641 sepol_context_set_mls(sepolh, context, user->level) < 0) { 642 goto fail; 643 } 644 645 if (user->homedir_role && 646 sepol_context_set_role(sepolh, context, user->homedir_role) < 0) { 647 goto fail; 648 } 649 650 if (sepol_context_to_string(sepolh, context, 651 &new_context_str) < 0) { 652 goto fail; 653 } 654 655 temp = semanage_str_replace(old_context_str, new_context_str, 656 line, 1); 657 if (!temp) { 658 goto fail; 659 } 660 free(line); 661 line = temp; 662 663 if (check_line(s, line) == STATUS_SUCCESS) { 664 if (fprintf(out, "%s\n", line) < 0) 665 goto fail; 666 } 667 668 free(line); 669 sepol_context_free(context); 670 free(new_context_str); 671 } 672 673 return STATUS_SUCCESS; 674 fail: 675 free(line); 676 sepol_context_free(context); 677 free(new_context_str); 678 return STATUS_ERR; 679 } 680 681 static int write_home_dir_context(genhomedircon_settings_t * s, FILE * out, 682 semanage_list_t * tpl, const genhomedircon_user_entry_t *user) 683 { 684 replacement_pair_t repl[] = { 685 {.search_for = TEMPLATE_HOME_DIR,.replace_with = user->home}, 686 {.search_for = TEMPLATE_ROLE,.replace_with = user->prefix}, 687 {NULL, NULL} 688 }; 689 690 if (strcmp(user->name, FALLBACK_NAME) == 0) { 691 if (fprintf(out, COMMENT_USER_HOME_CONTEXT, FALLBACK_SENAME) < 0) 692 return STATUS_ERR; 693 } else { 694 if (fprintf(out, COMMENT_USER_HOME_CONTEXT, user->name) < 0) 695 return STATUS_ERR; 696 } 697 698 return write_contexts(s, out, tpl, repl, user); 699 } 700 701 static int write_home_root_context(genhomedircon_settings_t * s, FILE * out, 702 semanage_list_t * tpl, char *homedir) 703 { 704 replacement_pair_t repl[] = { 705 {.search_for = TEMPLATE_HOME_ROOT,.replace_with = homedir}, 706 {NULL, NULL} 707 }; 708 709 return write_replacements(s, out, tpl, repl); 710 } 711 712 static int write_username_context(genhomedircon_settings_t * s, FILE * out, 713 semanage_list_t * tpl, 714 const genhomedircon_user_entry_t *user) 715 { 716 replacement_pair_t repl[] = { 717 {.search_for = TEMPLATE_USERNAME,.replace_with = user->name}, 718 {.search_for = TEMPLATE_USERID,.replace_with = user->uid}, 719 {.search_for = TEMPLATE_ROLE,.replace_with = user->prefix}, 720 {NULL, NULL} 721 }; 722 723 return write_contexts(s, out, tpl, repl, user); 724 } 725 726 static int write_user_context(genhomedircon_settings_t * s, FILE * out, 727 semanage_list_t * tpl, const genhomedircon_user_entry_t *user) 728 { 729 replacement_pair_t repl[] = { 730 {.search_for = TEMPLATE_USER,.replace_with = user->name}, 731 {.search_for = TEMPLATE_ROLE,.replace_with = user->prefix}, 732 {NULL, NULL} 733 }; 734 735 return write_contexts(s, out, tpl, repl, user); 736 } 737 738 static int seuser_sort_func(const void *arg1, const void *arg2) 739 { 740 const semanage_seuser_t **u1 = (const semanage_seuser_t **) arg1; 741 const semanage_seuser_t **u2 = (const semanage_seuser_t **) arg2;; 742 const char *name1 = semanage_seuser_get_name(*u1); 743 const char *name2 = semanage_seuser_get_name(*u2); 744 745 if (name1[0] == '%' && name2[0] == '%') { 746 return 0; 747 } else if (name1[0] == '%') { 748 return 1; 749 } else if (name2[0] == '%') { 750 return -1; 751 } 752 753 return strcmp(name1, name2); 754 } 755 756 static int user_sort_func(semanage_user_t ** arg1, semanage_user_t ** arg2) 757 { 758 return strcmp(semanage_user_get_name(*arg1), 759 semanage_user_get_name(*arg2)); 760 } 761 762 static int name_user_cmp(char *key, semanage_user_t ** val) 763 { 764 return strcmp(key, semanage_user_get_name(*val)); 765 } 766 767 static int push_user_entry(genhomedircon_user_entry_t ** list, const char *n, 768 const char *u, const char *g, const char *sen, 769 const char *pre, const char *h, const char *l, 770 const char *ln, const char *hd_role) 771 { 772 genhomedircon_user_entry_t *temp = NULL; 773 char *name = NULL; 774 char *uid = NULL; 775 char *gid = NULL; 776 char *sename = NULL; 777 char *prefix = NULL; 778 char *home = NULL; 779 char *level = NULL; 780 char *lname = NULL; 781 char *homedir_role = NULL; 782 783 temp = malloc(sizeof(genhomedircon_user_entry_t)); 784 if (!temp) 785 goto cleanup; 786 name = strdup(n); 787 if (!name) 788 goto cleanup; 789 uid = strdup(u); 790 if (!uid) 791 goto cleanup; 792 gid = strdup(g); 793 if (!gid) 794 goto cleanup; 795 sename = strdup(sen); 796 if (!sename) 797 goto cleanup; 798 prefix = strdup(pre); 799 if (!prefix) 800 goto cleanup; 801 home = strdup(h); 802 if (!home) 803 goto cleanup; 804 level = strdup(l); 805 if (!level) 806 goto cleanup; 807 lname = strdup(ln); 808 if (!lname) 809 goto cleanup; 810 if (hd_role) { 811 homedir_role = strdup(hd_role); 812 if (!homedir_role) 813 goto cleanup; 814 } 815 816 temp->name = name; 817 temp->uid = uid; 818 temp->gid = gid; 819 temp->sename = sename; 820 temp->prefix = prefix; 821 temp->home = home; 822 temp->level = level; 823 temp->login = lname; 824 temp->homedir_role = homedir_role; 825 temp->next = (*list); 826 (*list) = temp; 827 828 return STATUS_SUCCESS; 829 830 cleanup: 831 free(name); 832 free(uid); 833 free(gid); 834 free(sename); 835 free(prefix); 836 free(home); 837 free(level); 838 free(lname); 839 free(homedir_role); 840 free(temp); 841 return STATUS_ERR; 842 } 843 844 static void pop_user_entry(genhomedircon_user_entry_t ** list) 845 { 846 genhomedircon_user_entry_t *temp; 847 848 if (!list || !(*list)) 849 return; 850 851 temp = *list; 852 *list = temp->next; 853 free(temp->name); 854 free(temp->uid); 855 free(temp->gid); 856 free(temp->sename); 857 free(temp->prefix); 858 free(temp->home); 859 free(temp->level); 860 free(temp->login); 861 free(temp->homedir_role); 862 free(temp); 863 } 864 865 static int setup_fallback_user(genhomedircon_settings_t * s) 866 { 867 semanage_seuser_t **seuser_list = NULL; 868 unsigned int nseusers = 0; 869 semanage_user_key_t *key = NULL; 870 semanage_user_t *u = NULL; 871 const char *name = NULL; 872 const char *seuname = NULL; 873 const char *prefix = NULL; 874 const char *level = NULL; 875 const char *homedir_role = NULL; 876 unsigned int i; 877 int retval; 878 int errors = 0; 879 880 retval = semanage_seuser_list(s->h_semanage, &seuser_list, &nseusers); 881 if (retval < 0 || (nseusers < 1)) { 882 /* if there are no users, this function can't do any other work */ 883 return errors; 884 } 885 886 for (i = 0; i < nseusers; i++) { 887 name = semanage_seuser_get_name(seuser_list[i]); 888 if (strcmp(name, DEFAULT_LOGIN) == 0) { 889 seuname = semanage_seuser_get_sename(seuser_list[i]); 890 891 /* find the user structure given the name */ 892 if (semanage_user_key_create(s->h_semanage, seuname, 893 &key) < 0) { 894 errors = STATUS_ERR; 895 break; 896 } 897 if (semanage_user_query(s->h_semanage, key, &u) < 0) 898 { 899 prefix = name; 900 level = FALLBACK_LEVEL; 901 } 902 else 903 { 904 prefix = semanage_user_get_prefix(u); 905 level = semanage_user_get_mlslevel(u); 906 if (!level) 907 level = FALLBACK_LEVEL; 908 } 909 910 if (prefix_is_homedir_role(u, prefix)) { 911 homedir_role = prefix; 912 } 913 914 if (push_user_entry(&(s->fallback), FALLBACK_NAME, 915 FALLBACK_UIDGID, FALLBACK_UIDGID, 916 seuname, prefix, "", level, 917 FALLBACK_NAME, homedir_role) != 0) 918 errors = STATUS_ERR; 919 semanage_user_key_free(key); 920 if (u) 921 semanage_user_free(u); 922 break; 923 } 924 } 925 926 for (i = 0; i < nseusers; i++) 927 semanage_seuser_free(seuser_list[i]); 928 free(seuser_list); 929 930 return errors; 931 } 932 933 static genhomedircon_user_entry_t *find_user(genhomedircon_user_entry_t *head, 934 const char *name) 935 { 936 for(; head; head = head->next) { 937 if (strcmp(head->name, name) == 0) { 938 return head; 939 } 940 } 941 942 return NULL; 943 } 944 945 static int add_user(genhomedircon_settings_t * s, 946 genhomedircon_user_entry_t **head, 947 semanage_user_t *user, 948 const char *name, 949 const char *sename, 950 const char *selogin) 951 { 952 if (selogin[0] == '%') { 953 genhomedircon_user_entry_t *orig = find_user(*head, name); 954 if (orig != NULL && orig->login[0] == '%') { 955 ERR(s->h_semanage, "User %s is already mapped to" 956 " group %s, but also belongs to group %s. Add an" 957 " explicit mapping for this user to" 958 " override group mappings.", 959 name, orig->login + 1, selogin + 1); 960 return STATUS_ERR; 961 } else if (orig != NULL) { 962 // user mappings take precedence 963 return STATUS_SUCCESS; 964 } 965 } 966 967 int retval = STATUS_ERR; 968 969 char *rbuf = NULL; 970 long rbuflen; 971 struct passwd pwstorage, *pwent = NULL; 972 const char *prefix = NULL; 973 const char *level = NULL; 974 const char *homedir_role = NULL; 975 char uid[11]; 976 char gid[11]; 977 978 errno = 0; 979 /* Allocate space for the getpwnam_r buffer */ 980 rbuflen = sysconf(_SC_GETPW_R_SIZE_MAX); 981 if (rbuflen == -1 && errno == 0) 982 /* sysconf returning -1 with no errno means indeterminate size */ 983 rbuflen = 1024; 984 else if (rbuflen <= 0) 985 goto cleanup; 986 rbuf = malloc(rbuflen); 987 if (rbuf == NULL) 988 goto cleanup; 989 990 if (user) { 991 prefix = semanage_user_get_prefix(user); 992 level = semanage_user_get_mlslevel(user); 993 994 if (!level) { 995 level = FALLBACK_LEVEL; 996 } 997 } else { 998 prefix = name; 999 level = FALLBACK_LEVEL; 1000 } 1001 1002 if (prefix_is_homedir_role(user, prefix)) { 1003 homedir_role = prefix; 1004 } 1005 1006 retval = getpwnam_r(name, &pwstorage, rbuf, rbuflen, &pwent); 1007 if (retval != 0 || pwent == NULL) { 1008 if (retval != 0 && retval != ENOENT) { 1009 goto cleanup; 1010 } 1011 1012 WARN(s->h_semanage, 1013 "user %s not in password file", name); 1014 retval = STATUS_SUCCESS; 1015 goto cleanup; 1016 } 1017 1018 int len = strlen(pwent->pw_dir) -1; 1019 for(; len > 0 && pwent->pw_dir[len] == '/'; len--) { 1020 pwent->pw_dir[len] = '\0'; 1021 } 1022 1023 if (strcmp(pwent->pw_dir, "/") == 0) { 1024 /* don't relabel / genhomdircon checked to see if root 1025 * was the user and if so, set his home directory to 1026 * /root */ 1027 retval = STATUS_SUCCESS; 1028 goto cleanup; 1029 } 1030 1031 if (ignore(pwent->pw_dir)) { 1032 retval = STATUS_SUCCESS; 1033 goto cleanup; 1034 } 1035 1036 len = snprintf(uid, sizeof(uid), "%u", pwent->pw_uid); 1037 if (len < 0 || len >= (int)sizeof(uid)) { 1038 goto cleanup; 1039 } 1040 1041 len = snprintf(gid, sizeof(gid), "%u", pwent->pw_gid); 1042 if (len < 0 || len >= (int)sizeof(gid)) { 1043 goto cleanup; 1044 } 1045 1046 retval = push_user_entry(head, name, uid, gid, sename, prefix, 1047 pwent->pw_dir, level, selogin, homedir_role); 1048 cleanup: 1049 free(rbuf); 1050 return retval; 1051 } 1052 1053 static int get_group_users(genhomedircon_settings_t * s, 1054 genhomedircon_user_entry_t **head, 1055 semanage_user_t *user, 1056 const char *sename, 1057 const char *selogin) 1058 { 1059 int retval = STATUS_ERR; 1060 unsigned int i; 1061 1062 long grbuflen; 1063 char *grbuf = NULL; 1064 struct group grstorage, *group = NULL; 1065 struct passwd *pw = NULL; 1066 1067 errno = 0; 1068 grbuflen = sysconf(_SC_GETGR_R_SIZE_MAX); 1069 if (grbuflen == -1 && errno == 0) 1070 /* sysconf returning -1 with no errno means indeterminate size */ 1071 grbuflen = 1024; 1072 else if (grbuflen <= 0) 1073 goto cleanup; 1074 grbuf = malloc(grbuflen); 1075 if (grbuf == NULL) 1076 goto cleanup; 1077 1078 const char *grname = selogin + 1; 1079 1080 errno = 0; 1081 while ( 1082 (retval = getgrnam_r(grname, &grstorage, grbuf, (size_t) grbuflen, &group)) != 0 && 1083 errno == ERANGE 1084 ) { 1085 char *new_grbuf; 1086 grbuflen *= 2; 1087 if (grbuflen < 0) 1088 /* the member list could exceed 2Gb on a system with a 32-bit CPU (where 1089 * sizeof(long) = 4) - if this ever happened, the loop would become infinite. */ 1090 goto cleanup; 1091 new_grbuf = realloc(grbuf, grbuflen); 1092 if (new_grbuf == NULL) 1093 goto cleanup; 1094 grbuf = new_grbuf; 1095 } 1096 if (retval != 0) 1097 goto cleanup; 1098 1099 if (group == NULL) { 1100 ERR(s->h_semanage, "Can't find group named %s\n", grname); 1101 goto cleanup; 1102 } 1103 1104 size_t nmembers = 0; 1105 char **members = group->gr_mem; 1106 1107 while (*members != NULL) { 1108 nmembers++; 1109 members++; 1110 } 1111 1112 for (i = 0; i < nmembers; i++) { 1113 const char *uname = group->gr_mem[i]; 1114 1115 if (add_user(s, head, user, uname, sename, selogin) < 0) { 1116 goto cleanup; 1117 } 1118 } 1119 1120 setpwent(); 1121 while (1) { 1122 errno = 0; 1123 pw = getpwent(); 1124 if (pw == NULL) 1125 break; 1126 // skip users who also have this group as their 1127 // primary group 1128 if (lfind(pw->pw_name, group->gr_mem, &nmembers, 1129 sizeof(char *), &STR_COMPARATOR)) { 1130 continue; 1131 } 1132 1133 if (group->gr_gid == pw->pw_gid) { 1134 if (add_user(s, head, user, pw->pw_name, 1135 sename, selogin) < 0) { 1136 goto cleanup; 1137 } 1138 } 1139 } 1140 1141 retval = STATUS_SUCCESS; 1142 cleanup: 1143 endpwent(); 1144 free(grbuf); 1145 1146 return retval; 1147 } 1148 1149 static genhomedircon_user_entry_t *get_users(genhomedircon_settings_t * s, 1150 int *errors) 1151 { 1152 genhomedircon_user_entry_t *head = NULL; 1153 semanage_seuser_t **seuser_list = NULL; 1154 unsigned int nseusers = 0; 1155 semanage_user_t **user_list = NULL; 1156 unsigned int nusers = 0; 1157 semanage_user_t **u = NULL; 1158 const char *name = NULL; 1159 const char *seuname = NULL; 1160 unsigned int i; 1161 int retval; 1162 1163 *errors = 0; 1164 retval = semanage_seuser_list(s->h_semanage, &seuser_list, &nseusers); 1165 if (retval < 0 || (nseusers < 1)) { 1166 /* if there are no users, this function can't do any other work */ 1167 return NULL; 1168 } 1169 1170 if (semanage_user_list(s->h_semanage, &user_list, &nusers) < 0) { 1171 nusers = 0; 1172 } 1173 1174 qsort(seuser_list, nseusers, sizeof(semanage_seuser_t *), 1175 &seuser_sort_func); 1176 qsort(user_list, nusers, sizeof(semanage_user_t *), 1177 (int (*)(const void *, const void *))&user_sort_func); 1178 1179 for (i = 0; i < nseusers; i++) { 1180 seuname = semanage_seuser_get_sename(seuser_list[i]); 1181 name = semanage_seuser_get_name(seuser_list[i]); 1182 1183 if (strcmp(name, DEFAULT_LOGIN) == 0) 1184 continue; 1185 1186 /* find the user structure given the name */ 1187 u = bsearch(seuname, user_list, nusers, sizeof(semanage_user_t *), 1188 (int (*)(const void *, const void *)) 1189 &name_user_cmp); 1190 1191 /* %groupname syntax */ 1192 if (name[0] == '%') { 1193 retval = get_group_users(s, &head, *u, seuname, 1194 name); 1195 } else { 1196 retval = add_user(s, &head, *u, name, 1197 seuname, name); 1198 } 1199 1200 if (retval != 0) { 1201 *errors = STATUS_ERR; 1202 goto cleanup; 1203 } 1204 } 1205 1206 cleanup: 1207 if (*errors) { 1208 for (; head; pop_user_entry(&head)) { 1209 /* the pop function takes care of all the cleanup 1210 so the loop body is just empty */ 1211 } 1212 } 1213 for (i = 0; i < nseusers; i++) { 1214 semanage_seuser_free(seuser_list[i]); 1215 } 1216 free(seuser_list); 1217 1218 for (i = 0; i < nusers; i++) { 1219 semanage_user_free(user_list[i]); 1220 } 1221 free(user_list); 1222 1223 return head; 1224 } 1225 1226 static int write_gen_home_dir_context(genhomedircon_settings_t * s, FILE * out, 1227 semanage_list_t * username_context_tpl, 1228 semanage_list_t * user_context_tpl, 1229 semanage_list_t * homedir_context_tpl) 1230 { 1231 genhomedircon_user_entry_t *users; 1232 int errors = 0; 1233 1234 users = get_users(s, &errors); 1235 if (!users && errors) { 1236 return STATUS_ERR; 1237 } 1238 1239 for (; users; pop_user_entry(&users)) { 1240 if (write_home_dir_context(s, out, homedir_context_tpl, users)) 1241 goto err; 1242 if (write_username_context(s, out, username_context_tpl, users)) 1243 goto err; 1244 if (write_user_context(s, out, user_context_tpl, users)) 1245 goto err; 1246 } 1247 1248 return STATUS_SUCCESS; 1249 err: 1250 for (; users; pop_user_entry(&users)) { 1251 /* the pop function takes care of all the cleanup 1252 * so the loop body is just empty */ 1253 } 1254 1255 return STATUS_ERR; 1256 } 1257 1258 /** 1259 * @param s settings structure, stores various paths etc. Must never be NULL 1260 * @param out the FILE to put all the output in. 1261 * @return 0 on success 1262 */ 1263 static int write_context_file(genhomedircon_settings_t * s, FILE * out) 1264 { 1265 semanage_list_t *homedirs = NULL; 1266 semanage_list_t *h = NULL; 1267 semanage_list_t *homedir_context_tpl = NULL; 1268 semanage_list_t *homeroot_context_tpl = NULL; 1269 semanage_list_t *username_context_tpl = NULL; 1270 semanage_list_t *user_context_tpl = NULL; 1271 int retval = STATUS_SUCCESS; 1272 1273 homedir_context_tpl = make_template(s, &HOME_DIR_PRED); 1274 homeroot_context_tpl = make_template(s, &HOME_ROOT_PRED); 1275 username_context_tpl = make_template(s, &USERNAME_CONTEXT_PRED); 1276 user_context_tpl = make_template(s, &USER_CONTEXT_PRED); 1277 1278 if (!homedir_context_tpl 1279 && !homeroot_context_tpl 1280 && !username_context_tpl 1281 && !user_context_tpl) 1282 goto done; 1283 1284 if (write_file_context_header(out) != STATUS_SUCCESS) { 1285 retval = STATUS_ERR; 1286 goto done; 1287 } 1288 1289 if (setup_fallback_user(s) != 0) { 1290 retval = STATUS_ERR; 1291 goto done; 1292 } 1293 1294 if (homedir_context_tpl || homeroot_context_tpl) { 1295 homedirs = get_home_dirs(s); 1296 if (!homedirs) { 1297 WARN(s->h_semanage, 1298 "no home directories were available, exiting without writing"); 1299 goto done; 1300 } 1301 1302 for (h = homedirs; h; h = h->next) { 1303 char *temp = NULL; 1304 1305 if (asprintf(&temp, "%s/%s", h->data, FALLBACK_NAME) < 0) { 1306 retval = STATUS_ERR; 1307 goto done; 1308 } 1309 1310 free(s->fallback->home); 1311 s->fallback->home = temp; 1312 1313 if (write_home_dir_context(s, out, homedir_context_tpl, 1314 s->fallback) != STATUS_SUCCESS) { 1315 free(temp); 1316 s->fallback->home = NULL; 1317 retval = STATUS_ERR; 1318 goto done; 1319 } 1320 if (write_home_root_context(s, out, 1321 homeroot_context_tpl, 1322 h->data) != STATUS_SUCCESS) { 1323 free(temp); 1324 s->fallback->home = NULL; 1325 retval = STATUS_ERR; 1326 goto done; 1327 } 1328 1329 free(temp); 1330 s->fallback->home = NULL; 1331 } 1332 } 1333 if (user_context_tpl || username_context_tpl) { 1334 if (write_username_context(s, out, username_context_tpl, 1335 s->fallback) != STATUS_SUCCESS) { 1336 retval = STATUS_ERR; 1337 goto done; 1338 } 1339 1340 if (write_user_context(s, out, user_context_tpl, 1341 s->fallback) != STATUS_SUCCESS) { 1342 retval = STATUS_ERR; 1343 goto done; 1344 } 1345 1346 if (write_gen_home_dir_context(s, out, username_context_tpl, 1347 user_context_tpl, homedir_context_tpl) 1348 != STATUS_SUCCESS) { 1349 retval = STATUS_ERR; 1350 } 1351 } 1352 1353 done: 1354 /* Cleanup */ 1355 semanage_list_destroy(&homedirs); 1356 semanage_list_destroy(&username_context_tpl); 1357 semanage_list_destroy(&user_context_tpl); 1358 semanage_list_destroy(&homedir_context_tpl); 1359 semanage_list_destroy(&homeroot_context_tpl); 1360 1361 return retval; 1362 } 1363 1364 int semanage_genhomedircon(semanage_handle_t * sh, 1365 sepol_policydb_t * policydb, 1366 int usepasswd, 1367 char *ignoredirs) 1368 { 1369 genhomedircon_settings_t s; 1370 FILE *out = NULL; 1371 int retval = 0; 1372 1373 assert(sh); 1374 1375 s.homedir_template_path = 1376 semanage_path(SEMANAGE_TMP, SEMANAGE_HOMEDIR_TMPL); 1377 s.fcfilepath = 1378 semanage_path(SEMANAGE_TMP, SEMANAGE_STORE_FC_HOMEDIRS); 1379 1380 s.fallback = calloc(1, sizeof(genhomedircon_user_entry_t)); 1381 if (s.fallback == NULL) { 1382 retval = STATUS_ERR; 1383 goto done; 1384 } 1385 1386 s.fallback->name = strdup(FALLBACK_NAME); 1387 s.fallback->sename = strdup(FALLBACK_SENAME); 1388 s.fallback->prefix = strdup(FALLBACK_PREFIX); 1389 s.fallback->level = strdup(FALLBACK_LEVEL); 1390 if (s.fallback->name == NULL 1391 || s.fallback->sename == NULL 1392 || s.fallback->prefix == NULL 1393 || s.fallback->level == NULL) { 1394 retval = STATUS_ERR; 1395 goto done; 1396 } 1397 1398 if (ignoredirs) ignore_setup(ignoredirs); 1399 1400 s.usepasswd = usepasswd; 1401 s.h_semanage = sh; 1402 s.policydb = policydb; 1403 1404 if (!(out = fopen(s.fcfilepath, "w"))) { 1405 /* couldn't open output file */ 1406 ERR(sh, "Could not open the file_context file for writing"); 1407 retval = STATUS_ERR; 1408 goto done; 1409 } 1410 1411 retval = write_context_file(&s, out); 1412 1413 done: 1414 if (out != NULL) 1415 fclose(out); 1416 1417 while (s.fallback) 1418 pop_user_entry(&(s.fallback)); 1419 1420 ignore_free(); 1421 1422 return retval; 1423 } 1424