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 ((pwbuf = getpwent()) != NULL) { 365 if (pwbuf->pw_uid < minuid || pwbuf->pw_uid > maxuid) 366 continue; 367 if (!semanage_list_find(shells, pwbuf->pw_shell)) 368 continue; 369 int len = strlen(pwbuf->pw_dir) -1; 370 for(; len > 0 && pwbuf->pw_dir[len] == '/'; len--) { 371 pwbuf->pw_dir[len] = '\0'; 372 } 373 if (strcmp(pwbuf->pw_dir, "/") == 0) 374 continue; 375 if (ignore(pwbuf->pw_dir)) 376 continue; 377 if (semanage_str_count(pwbuf->pw_dir, '/') <= 1) 378 continue; 379 if (!(path = strdup(pwbuf->pw_dir))) { 380 break; 381 } 382 383 semanage_rtrim(path, '/'); 384 385 if (!semanage_list_find(homedir_list, path)) { 386 /* 387 * Now check for an existing file context that matches 388 * so we don't label a non-homedir as a homedir. 389 */ 390 hand.dir = path; 391 hand.matched = 0; 392 if (semanage_fcontext_iterate(s->h_semanage, 393 fcontext_matches, &hand) == STATUS_ERR) 394 goto fail; 395 396 /* NOTE: old genhomedircon printed a warning on match */ 397 if (hand.matched) { 398 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); 399 } else { 400 if (semanage_list_push(&homedir_list, path)) 401 goto fail; 402 } 403 } 404 free(path); 405 path = NULL; 406 errno = 0; 407 } 408 409 if (errno) { 410 WARN(s->h_semanage, "Error while fetching users. " 411 "Returning list so far."); 412 } 413 414 if (semanage_list_sort(&homedir_list)) 415 goto fail; 416 417 endpwent(); 418 semanage_list_destroy(&shells); 419 420 return homedir_list; 421 422 fail: 423 endpwent(); 424 free(path); 425 semanage_list_destroy(&homedir_list); 426 semanage_list_destroy(&shells); 427 return NULL; 428 } 429 430 /** 431 * @param out the FILE to put all the output in. 432 * @return 0 on success 433 */ 434 static int write_file_context_header(FILE * out) 435 { 436 if (fprintf(out, COMMENT_FILE_CONTEXT_HEADER) < 0) { 437 return STATUS_ERR; 438 } 439 440 return STATUS_SUCCESS; 441 } 442 443 /* Predicates for use with semanage_slurp_file_filter() the homedir_template 444 * file currently contains lines that serve as the template for a user's 445 * homedir. 446 * 447 * It also contains lines that are the template for the parent of a 448 * user's home directory. 449 * 450 * Currently, the only lines that apply to the the root of a user's home 451 * directory are all prefixed with the string "HOME_ROOT". All other 452 * lines apply to a user's home directory. If this changes the 453 * following predicates need to change to reflect that. 454 */ 455 static int HOME_ROOT_PRED(const char *string) 456 { 457 return semanage_is_prefix(string, TEMPLATE_HOME_ROOT); 458 } 459 460 static int HOME_DIR_PRED(const char *string) 461 { 462 return semanage_is_prefix(string, TEMPLATE_HOME_DIR); 463 } 464 465 /* new names */ 466 static int USERNAME_CONTEXT_PRED(const char *string) 467 { 468 return (int)( 469 (strstr(string, TEMPLATE_USERNAME) != NULL) || 470 (strstr(string, TEMPLATE_USERID) != NULL) 471 ); 472 } 473 474 /* This will never match USER if USERNAME or USERID are found. */ 475 static int USER_CONTEXT_PRED(const char *string) 476 { 477 if (USERNAME_CONTEXT_PRED(string)) 478 return 0; 479 480 return (int)(strstr(string, TEMPLATE_USER) != NULL); 481 } 482 483 static int STR_COMPARATOR(const void *a, const void *b) 484 { 485 return strcmp((const char *) a, (const char *) b); 486 } 487 488 /* make_tempate 489 * @param s the settings holding the paths to various files 490 * @param pred function pointer to function to use as filter for slurp 491 * file filter 492 * @return a list of lines from the template file with inappropriate 493 * lines filtered out. 494 */ 495 static semanage_list_t *make_template(genhomedircon_settings_t * s, 496 int (*pred) (const char *)) 497 { 498 FILE *template_file = NULL; 499 semanage_list_t *template_data = NULL; 500 501 template_file = fopen(s->homedir_template_path, "r"); 502 if (!template_file) 503 return NULL; 504 template_data = semanage_slurp_file_filter(template_file, pred); 505 fclose(template_file); 506 507 return template_data; 508 } 509 510 static char *replace_all(const char *str, const replacement_pair_t * repl) 511 { 512 char *retval, *retval2; 513 int i; 514 515 if (!str || !repl) 516 return NULL; 517 518 retval = strdup(str); 519 for (i = 0; retval != NULL && repl[i].search_for; i++) { 520 retval2 = semanage_str_replace(repl[i].search_for, 521 repl[i].replace_with, retval, 0); 522 free(retval); 523 retval = retval2; 524 } 525 return retval; 526 } 527 528 static const char *extract_context(const char *line) 529 { 530 const char *p = line; 531 size_t off; 532 533 off = strlen(p); 534 p += off; 535 /* consider trailing whitespaces */ 536 while (off > 0) { 537 p--; 538 off--; 539 if (!isspace(*p)) 540 break; 541 } 542 if (off == 0) 543 return NULL; 544 545 /* find the last field in line */ 546 while (off > 0 && !isspace(*(p - 1))) { 547 p--; 548 off--; 549 } 550 return p; 551 } 552 553 static int check_line(genhomedircon_settings_t * s, const char *line) 554 { 555 sepol_context_t *ctx_record = NULL; 556 const char *ctx_str; 557 int result; 558 559 ctx_str = extract_context(line); 560 if (!ctx_str) 561 return STATUS_ERR; 562 563 result = sepol_context_from_string(s->h_semanage->sepolh, 564 ctx_str, &ctx_record); 565 if (result == STATUS_SUCCESS && ctx_record != NULL) { 566 result = sepol_context_check(s->h_semanage->sepolh, 567 s->policydb, ctx_record); 568 sepol_context_free(ctx_record); 569 } 570 return result; 571 } 572 573 static int write_replacements(genhomedircon_settings_t * s, FILE * out, 574 const semanage_list_t * tpl, 575 const replacement_pair_t *repl) 576 { 577 char *line; 578 579 for (; tpl; tpl = tpl->next) { 580 line = replace_all(tpl->data, repl); 581 if (!line) 582 goto fail; 583 if (check_line(s, line) == STATUS_SUCCESS) { 584 if (fprintf(out, "%s\n", line) < 0) 585 goto fail; 586 } 587 free(line); 588 } 589 return STATUS_SUCCESS; 590 591 fail: 592 free(line); 593 return STATUS_ERR; 594 } 595 596 static int write_contexts(genhomedircon_settings_t *s, FILE *out, 597 semanage_list_t *tpl, const replacement_pair_t *repl, 598 const genhomedircon_user_entry_t *user) 599 { 600 char *line, *temp; 601 sepol_context_t *context; 602 char *new_context_str; 603 604 for (; tpl; tpl = tpl->next) { 605 context = NULL; 606 new_context_str = NULL; 607 line = replace_all(tpl->data, repl); 608 if (!line) { 609 goto fail; 610 } 611 612 const char *old_context_str = extract_context(line); 613 if (!old_context_str) { 614 goto fail; 615 } 616 617 if (strcmp(old_context_str, CONTEXT_NONE) == 0) { 618 if (check_line(s, line) == STATUS_SUCCESS && 619 fprintf(out, "%s\n", line) < 0) { 620 goto fail; 621 } 622 free(line); 623 continue; 624 } 625 626 sepol_handle_t *sepolh = s->h_semanage->sepolh; 627 628 if (sepol_context_from_string(sepolh, old_context_str, 629 &context) < 0) { 630 goto fail; 631 } 632 633 if (sepol_context_set_user(sepolh, context, user->sename) < 0) { 634 goto fail; 635 } 636 637 if (sepol_policydb_mls_enabled(s->policydb) && 638 sepol_context_set_mls(sepolh, context, user->level) < 0) { 639 goto fail; 640 } 641 642 if (user->homedir_role && 643 sepol_context_set_role(sepolh, context, user->homedir_role) < 0) { 644 goto fail; 645 } 646 647 if (sepol_context_to_string(sepolh, context, 648 &new_context_str) < 0) { 649 goto fail; 650 } 651 652 temp = semanage_str_replace(old_context_str, new_context_str, 653 line, 1); 654 if (!temp) { 655 goto fail; 656 } 657 free(line); 658 line = temp; 659 660 if (check_line(s, line) == STATUS_SUCCESS) { 661 if (fprintf(out, "%s\n", line) < 0) 662 goto fail; 663 } 664 665 free(line); 666 sepol_context_free(context); 667 free(new_context_str); 668 } 669 670 return STATUS_SUCCESS; 671 fail: 672 free(line); 673 sepol_context_free(context); 674 free(new_context_str); 675 return STATUS_ERR; 676 } 677 678 static int write_home_dir_context(genhomedircon_settings_t * s, FILE * out, 679 semanage_list_t * tpl, const genhomedircon_user_entry_t *user) 680 { 681 replacement_pair_t repl[] = { 682 {.search_for = TEMPLATE_HOME_DIR,.replace_with = user->home}, 683 {.search_for = TEMPLATE_ROLE,.replace_with = user->prefix}, 684 {NULL, NULL} 685 }; 686 687 if (strcmp(user->name, FALLBACK_NAME) == 0) { 688 if (fprintf(out, COMMENT_USER_HOME_CONTEXT, FALLBACK_SENAME) < 0) 689 return STATUS_ERR; 690 } else { 691 if (fprintf(out, COMMENT_USER_HOME_CONTEXT, user->name) < 0) 692 return STATUS_ERR; 693 } 694 695 return write_contexts(s, out, tpl, repl, user); 696 } 697 698 static int write_home_root_context(genhomedircon_settings_t * s, FILE * out, 699 semanage_list_t * tpl, char *homedir) 700 { 701 replacement_pair_t repl[] = { 702 {.search_for = TEMPLATE_HOME_ROOT,.replace_with = homedir}, 703 {NULL, NULL} 704 }; 705 706 return write_replacements(s, out, tpl, repl); 707 } 708 709 static int write_username_context(genhomedircon_settings_t * s, FILE * out, 710 semanage_list_t * tpl, 711 const genhomedircon_user_entry_t *user) 712 { 713 replacement_pair_t repl[] = { 714 {.search_for = TEMPLATE_USERNAME,.replace_with = user->name}, 715 {.search_for = TEMPLATE_USERID,.replace_with = user->uid}, 716 {.search_for = TEMPLATE_ROLE,.replace_with = user->prefix}, 717 {NULL, NULL} 718 }; 719 720 return write_contexts(s, out, tpl, repl, user); 721 } 722 723 static int write_user_context(genhomedircon_settings_t * s, FILE * out, 724 semanage_list_t * tpl, const genhomedircon_user_entry_t *user) 725 { 726 replacement_pair_t repl[] = { 727 {.search_for = TEMPLATE_USER,.replace_with = user->name}, 728 {.search_for = TEMPLATE_ROLE,.replace_with = user->prefix}, 729 {NULL, NULL} 730 }; 731 732 return write_contexts(s, out, tpl, repl, user); 733 } 734 735 static int seuser_sort_func(const void *arg1, const void *arg2) 736 { 737 const semanage_seuser_t **u1 = (const semanage_seuser_t **) arg1; 738 const semanage_seuser_t **u2 = (const semanage_seuser_t **) arg2;; 739 const char *name1 = semanage_seuser_get_name(*u1); 740 const char *name2 = semanage_seuser_get_name(*u2); 741 742 if (name1[0] == '%' && name2[0] == '%') { 743 return 0; 744 } else if (name1[0] == '%') { 745 return 1; 746 } else if (name2[0] == '%') { 747 return -1; 748 } 749 750 return strcmp(name1, name2); 751 } 752 753 static int user_sort_func(semanage_user_t ** arg1, semanage_user_t ** arg2) 754 { 755 return strcmp(semanage_user_get_name(*arg1), 756 semanage_user_get_name(*arg2)); 757 } 758 759 static int name_user_cmp(char *key, semanage_user_t ** val) 760 { 761 return strcmp(key, semanage_user_get_name(*val)); 762 } 763 764 static int push_user_entry(genhomedircon_user_entry_t ** list, const char *n, 765 const char *u, const char *g, const char *sen, 766 const char *pre, const char *h, const char *l, 767 const char *ln, const char *hd_role) 768 { 769 genhomedircon_user_entry_t *temp = NULL; 770 char *name = NULL; 771 char *uid = NULL; 772 char *gid = NULL; 773 char *sename = NULL; 774 char *prefix = NULL; 775 char *home = NULL; 776 char *level = NULL; 777 char *lname = NULL; 778 char *homedir_role = NULL; 779 780 temp = malloc(sizeof(genhomedircon_user_entry_t)); 781 if (!temp) 782 goto cleanup; 783 name = strdup(n); 784 if (!name) 785 goto cleanup; 786 uid = strdup(u); 787 if (!uid) 788 goto cleanup; 789 gid = strdup(g); 790 if (!gid) 791 goto cleanup; 792 sename = strdup(sen); 793 if (!sename) 794 goto cleanup; 795 prefix = strdup(pre); 796 if (!prefix) 797 goto cleanup; 798 home = strdup(h); 799 if (!home) 800 goto cleanup; 801 level = strdup(l); 802 if (!level) 803 goto cleanup; 804 lname = strdup(ln); 805 if (!lname) 806 goto cleanup; 807 if (hd_role) { 808 homedir_role = strdup(hd_role); 809 if (!homedir_role) 810 goto cleanup; 811 } 812 813 temp->name = name; 814 temp->uid = uid; 815 temp->gid = gid; 816 temp->sename = sename; 817 temp->prefix = prefix; 818 temp->home = home; 819 temp->level = level; 820 temp->login = lname; 821 temp->homedir_role = homedir_role; 822 temp->next = (*list); 823 (*list) = temp; 824 825 return STATUS_SUCCESS; 826 827 cleanup: 828 free(name); 829 free(uid); 830 free(gid); 831 free(sename); 832 free(prefix); 833 free(home); 834 free(level); 835 free(lname); 836 free(homedir_role); 837 free(temp); 838 return STATUS_ERR; 839 } 840 841 static void pop_user_entry(genhomedircon_user_entry_t ** list) 842 { 843 genhomedircon_user_entry_t *temp; 844 845 if (!list || !(*list)) 846 return; 847 848 temp = *list; 849 *list = temp->next; 850 free(temp->name); 851 free(temp->uid); 852 free(temp->gid); 853 free(temp->sename); 854 free(temp->prefix); 855 free(temp->home); 856 free(temp->level); 857 free(temp->login); 858 free(temp->homedir_role); 859 free(temp); 860 } 861 862 static int setup_fallback_user(genhomedircon_settings_t * s) 863 { 864 semanage_seuser_t **seuser_list = NULL; 865 unsigned int nseusers = 0; 866 semanage_user_key_t *key = NULL; 867 semanage_user_t *u = NULL; 868 const char *name = NULL; 869 const char *seuname = NULL; 870 const char *prefix = NULL; 871 const char *level = NULL; 872 const char *homedir_role = NULL; 873 unsigned int i; 874 int retval; 875 int errors = 0; 876 877 retval = semanage_seuser_list(s->h_semanage, &seuser_list, &nseusers); 878 if (retval < 0 || (nseusers < 1)) { 879 /* if there are no users, this function can't do any other work */ 880 return errors; 881 } 882 883 for (i = 0; i < nseusers; i++) { 884 name = semanage_seuser_get_name(seuser_list[i]); 885 if (strcmp(name, DEFAULT_LOGIN) == 0) { 886 seuname = semanage_seuser_get_sename(seuser_list[i]); 887 888 /* find the user structure given the name */ 889 if (semanage_user_key_create(s->h_semanage, seuname, 890 &key) < 0) { 891 errors = STATUS_ERR; 892 break; 893 } 894 if (semanage_user_query(s->h_semanage, key, &u) < 0) 895 { 896 prefix = name; 897 level = FALLBACK_LEVEL; 898 } 899 else 900 { 901 prefix = semanage_user_get_prefix(u); 902 level = semanage_user_get_mlslevel(u); 903 if (!level) 904 level = FALLBACK_LEVEL; 905 } 906 907 if (prefix_is_homedir_role(u, prefix)) { 908 homedir_role = prefix; 909 } 910 911 if (push_user_entry(&(s->fallback), FALLBACK_NAME, 912 FALLBACK_UIDGID, FALLBACK_UIDGID, 913 seuname, prefix, "", level, 914 FALLBACK_NAME, homedir_role) != 0) 915 errors = STATUS_ERR; 916 semanage_user_key_free(key); 917 if (u) 918 semanage_user_free(u); 919 break; 920 } 921 } 922 923 for (i = 0; i < nseusers; i++) 924 semanage_seuser_free(seuser_list[i]); 925 free(seuser_list); 926 927 return errors; 928 } 929 930 static genhomedircon_user_entry_t *find_user(genhomedircon_user_entry_t *head, 931 const char *name) 932 { 933 for(; head; head = head->next) { 934 if (strcmp(head->name, name) == 0) { 935 return head; 936 } 937 } 938 939 return NULL; 940 } 941 942 static int add_user(genhomedircon_settings_t * s, 943 genhomedircon_user_entry_t **head, 944 semanage_user_t *user, 945 const char *name, 946 const char *sename, 947 const char *selogin) 948 { 949 if (selogin[0] == '%') { 950 genhomedircon_user_entry_t *orig = find_user(*head, name); 951 if (orig != NULL && orig->login[0] == '%') { 952 ERR(s->h_semanage, "User %s is already mapped to" 953 " group %s, but also belongs to group %s. Add an" 954 " explicit mapping for this user to" 955 " override group mappings.", 956 name, orig->login + 1, selogin + 1); 957 return STATUS_ERR; 958 } else if (orig != NULL) { 959 // user mappings take precedence 960 return STATUS_SUCCESS; 961 } 962 } 963 964 int retval = STATUS_ERR; 965 966 char *rbuf = NULL; 967 long rbuflen; 968 struct passwd pwstorage, *pwent = NULL; 969 const char *prefix = NULL; 970 const char *level = NULL; 971 const char *homedir_role = NULL; 972 char uid[11]; 973 char gid[11]; 974 975 /* Allocate space for the getpwnam_r buffer */ 976 rbuflen = sysconf(_SC_GETPW_R_SIZE_MAX); 977 if (rbuflen <= 0) 978 goto cleanup; 979 rbuf = malloc(rbuflen); 980 if (rbuf == NULL) 981 goto cleanup; 982 983 if (user) { 984 prefix = semanage_user_get_prefix(user); 985 level = semanage_user_get_mlslevel(user); 986 987 if (!level) { 988 level = FALLBACK_LEVEL; 989 } 990 } else { 991 prefix = name; 992 level = FALLBACK_LEVEL; 993 } 994 995 if (prefix_is_homedir_role(user, prefix)) { 996 homedir_role = prefix; 997 } 998 999 retval = getpwnam_r(name, &pwstorage, rbuf, rbuflen, &pwent); 1000 if (retval != 0 || pwent == NULL) { 1001 if (retval != 0 && retval != ENOENT) { 1002 goto cleanup; 1003 } 1004 1005 WARN(s->h_semanage, 1006 "user %s not in password file", name); 1007 retval = STATUS_SUCCESS; 1008 goto cleanup; 1009 } 1010 1011 int len = strlen(pwent->pw_dir) -1; 1012 for(; len > 0 && pwent->pw_dir[len] == '/'; len--) { 1013 pwent->pw_dir[len] = '\0'; 1014 } 1015 1016 if (strcmp(pwent->pw_dir, "/") == 0) { 1017 /* don't relabel / genhomdircon checked to see if root 1018 * was the user and if so, set his home directory to 1019 * /root */ 1020 retval = STATUS_SUCCESS; 1021 goto cleanup; 1022 } 1023 1024 if (ignore(pwent->pw_dir)) { 1025 retval = STATUS_SUCCESS; 1026 goto cleanup; 1027 } 1028 1029 len = snprintf(uid, sizeof(uid), "%u", pwent->pw_uid); 1030 if (len < 0 || len >= (int)sizeof(uid)) { 1031 goto cleanup; 1032 } 1033 1034 len = snprintf(gid, sizeof(gid), "%u", pwent->pw_gid); 1035 if (len < 0 || len >= (int)sizeof(gid)) { 1036 goto cleanup; 1037 } 1038 1039 retval = push_user_entry(head, name, uid, gid, sename, prefix, 1040 pwent->pw_dir, level, selogin, homedir_role); 1041 cleanup: 1042 free(rbuf); 1043 return retval; 1044 } 1045 1046 static int get_group_users(genhomedircon_settings_t * s, 1047 genhomedircon_user_entry_t **head, 1048 semanage_user_t *user, 1049 const char *sename, 1050 const char *selogin) 1051 { 1052 int retval = STATUS_ERR; 1053 unsigned int i; 1054 1055 long grbuflen; 1056 char *grbuf = NULL; 1057 struct group grstorage, *group = NULL; 1058 struct passwd *pw = NULL; 1059 1060 grbuflen = sysconf(_SC_GETGR_R_SIZE_MAX); 1061 if (grbuflen <= 0) 1062 goto cleanup; 1063 grbuf = malloc(grbuflen); 1064 if (grbuf == NULL) 1065 goto cleanup; 1066 1067 const char *grname = selogin + 1; 1068 1069 if (getgrnam_r(grname, &grstorage, grbuf, 1070 (size_t) grbuflen, &group) != 0) { 1071 goto cleanup; 1072 } 1073 1074 if (group == NULL) { 1075 ERR(s->h_semanage, "Can't find group named %s\n", grname); 1076 goto cleanup; 1077 } 1078 1079 size_t nmembers = 0; 1080 char **members = group->gr_mem; 1081 1082 while (*members != NULL) { 1083 nmembers++; 1084 members++; 1085 } 1086 1087 for (i = 0; i < nmembers; i++) { 1088 const char *uname = group->gr_mem[i]; 1089 1090 if (add_user(s, head, user, uname, sename, selogin) < 0) { 1091 goto cleanup; 1092 } 1093 } 1094 1095 setpwent(); 1096 while ((pw = getpwent()) != NULL) { 1097 // skip users who also have this group as their 1098 // primary group 1099 if (lfind(pw->pw_name, group->gr_mem, &nmembers, 1100 sizeof(char *), &STR_COMPARATOR)) { 1101 continue; 1102 } 1103 1104 if (group->gr_gid == pw->pw_gid) { 1105 if (add_user(s, head, user, pw->pw_name, 1106 sename, selogin) < 0) { 1107 goto cleanup; 1108 } 1109 } 1110 } 1111 1112 retval = STATUS_SUCCESS; 1113 cleanup: 1114 endpwent(); 1115 free(grbuf); 1116 1117 return retval; 1118 } 1119 1120 static genhomedircon_user_entry_t *get_users(genhomedircon_settings_t * s, 1121 int *errors) 1122 { 1123 genhomedircon_user_entry_t *head = NULL; 1124 semanage_seuser_t **seuser_list = NULL; 1125 unsigned int nseusers = 0; 1126 semanage_user_t **user_list = NULL; 1127 unsigned int nusers = 0; 1128 semanage_user_t **u = NULL; 1129 const char *name = NULL; 1130 const char *seuname = NULL; 1131 unsigned int i; 1132 int retval; 1133 1134 *errors = 0; 1135 retval = semanage_seuser_list(s->h_semanage, &seuser_list, &nseusers); 1136 if (retval < 0 || (nseusers < 1)) { 1137 /* if there are no users, this function can't do any other work */ 1138 return NULL; 1139 } 1140 1141 if (semanage_user_list(s->h_semanage, &user_list, &nusers) < 0) { 1142 nusers = 0; 1143 } 1144 1145 qsort(seuser_list, nseusers, sizeof(semanage_seuser_t *), 1146 &seuser_sort_func); 1147 qsort(user_list, nusers, sizeof(semanage_user_t *), 1148 (int (*)(const void *, const void *))&user_sort_func); 1149 1150 for (i = 0; i < nseusers; i++) { 1151 seuname = semanage_seuser_get_sename(seuser_list[i]); 1152 name = semanage_seuser_get_name(seuser_list[i]); 1153 1154 if (strcmp(name, DEFAULT_LOGIN) == 0) 1155 continue; 1156 1157 /* find the user structure given the name */ 1158 u = bsearch(seuname, user_list, nusers, sizeof(semanage_user_t *), 1159 (int (*)(const void *, const void *)) 1160 &name_user_cmp); 1161 1162 /* %groupname syntax */ 1163 if (name[0] == '%') { 1164 retval = get_group_users(s, &head, *u, seuname, 1165 name); 1166 } else { 1167 retval = add_user(s, &head, *u, name, 1168 seuname, name); 1169 } 1170 1171 if (retval != 0) { 1172 *errors = STATUS_ERR; 1173 goto cleanup; 1174 } 1175 } 1176 1177 cleanup: 1178 if (*errors) { 1179 for (; head; pop_user_entry(&head)) { 1180 /* the pop function takes care of all the cleanup 1181 so the loop body is just empty */ 1182 } 1183 } 1184 for (i = 0; i < nseusers; i++) { 1185 semanage_seuser_free(seuser_list[i]); 1186 } 1187 free(seuser_list); 1188 1189 for (i = 0; i < nusers; i++) { 1190 semanage_user_free(user_list[i]); 1191 } 1192 free(user_list); 1193 1194 return head; 1195 } 1196 1197 static int write_gen_home_dir_context(genhomedircon_settings_t * s, FILE * out, 1198 semanage_list_t * username_context_tpl, 1199 semanage_list_t * user_context_tpl, 1200 semanage_list_t * homedir_context_tpl) 1201 { 1202 genhomedircon_user_entry_t *users; 1203 int errors = 0; 1204 1205 users = get_users(s, &errors); 1206 if (!users && errors) { 1207 return STATUS_ERR; 1208 } 1209 1210 for (; users; pop_user_entry(&users)) { 1211 if (write_home_dir_context(s, out, homedir_context_tpl, users)) 1212 goto err; 1213 if (write_username_context(s, out, username_context_tpl, users)) 1214 goto err; 1215 if (write_user_context(s, out, user_context_tpl, users)) 1216 goto err; 1217 } 1218 1219 return STATUS_SUCCESS; 1220 err: 1221 for (; users; pop_user_entry(&users)) { 1222 /* the pop function takes care of all the cleanup 1223 * so the loop body is just empty */ 1224 } 1225 1226 return STATUS_ERR; 1227 } 1228 1229 /** 1230 * @param s settings structure, stores various paths etc. Must never be NULL 1231 * @param out the FILE to put all the output in. 1232 * @return 0 on success 1233 */ 1234 static int write_context_file(genhomedircon_settings_t * s, FILE * out) 1235 { 1236 semanage_list_t *homedirs = NULL; 1237 semanage_list_t *h = NULL; 1238 semanage_list_t *homedir_context_tpl = NULL; 1239 semanage_list_t *homeroot_context_tpl = NULL; 1240 semanage_list_t *username_context_tpl = NULL; 1241 semanage_list_t *user_context_tpl = NULL; 1242 int retval = STATUS_SUCCESS; 1243 1244 homedir_context_tpl = make_template(s, &HOME_DIR_PRED); 1245 homeroot_context_tpl = make_template(s, &HOME_ROOT_PRED); 1246 username_context_tpl = make_template(s, &USERNAME_CONTEXT_PRED); 1247 user_context_tpl = make_template(s, &USER_CONTEXT_PRED); 1248 1249 if (!homedir_context_tpl 1250 && !homeroot_context_tpl 1251 && !username_context_tpl 1252 && !user_context_tpl) 1253 goto done; 1254 1255 if (write_file_context_header(out) != STATUS_SUCCESS) { 1256 retval = STATUS_ERR; 1257 goto done; 1258 } 1259 1260 if (setup_fallback_user(s) != 0) { 1261 retval = STATUS_ERR; 1262 goto done; 1263 } 1264 1265 if (homedir_context_tpl || homeroot_context_tpl) { 1266 homedirs = get_home_dirs(s); 1267 if (!homedirs) { 1268 WARN(s->h_semanage, 1269 "no home directories were available, exiting without writing"); 1270 goto done; 1271 } 1272 1273 for (h = homedirs; h; h = h->next) { 1274 char *temp = NULL; 1275 1276 if (asprintf(&temp, "%s/%s", h->data, FALLBACK_NAME) < 0) { 1277 retval = STATUS_ERR; 1278 goto done; 1279 } 1280 1281 free(s->fallback->home); 1282 s->fallback->home = temp; 1283 1284 if (write_home_dir_context(s, out, homedir_context_tpl, 1285 s->fallback) != STATUS_SUCCESS) { 1286 free(temp); 1287 s->fallback->home = NULL; 1288 retval = STATUS_ERR; 1289 goto done; 1290 } 1291 if (write_home_root_context(s, out, 1292 homeroot_context_tpl, 1293 h->data) != STATUS_SUCCESS) { 1294 free(temp); 1295 s->fallback->home = NULL; 1296 retval = STATUS_ERR; 1297 goto done; 1298 } 1299 1300 free(temp); 1301 s->fallback->home = NULL; 1302 } 1303 } 1304 if (user_context_tpl || username_context_tpl) { 1305 if (write_username_context(s, out, username_context_tpl, 1306 s->fallback) != STATUS_SUCCESS) { 1307 retval = STATUS_ERR; 1308 goto done; 1309 } 1310 1311 if (write_user_context(s, out, user_context_tpl, 1312 s->fallback) != STATUS_SUCCESS) { 1313 retval = STATUS_ERR; 1314 goto done; 1315 } 1316 1317 if (write_gen_home_dir_context(s, out, username_context_tpl, 1318 user_context_tpl, homedir_context_tpl) 1319 != STATUS_SUCCESS) { 1320 retval = STATUS_ERR; 1321 } 1322 } 1323 1324 done: 1325 /* Cleanup */ 1326 semanage_list_destroy(&homedirs); 1327 semanage_list_destroy(&username_context_tpl); 1328 semanage_list_destroy(&user_context_tpl); 1329 semanage_list_destroy(&homedir_context_tpl); 1330 semanage_list_destroy(&homeroot_context_tpl); 1331 1332 return retval; 1333 } 1334 1335 int semanage_genhomedircon(semanage_handle_t * sh, 1336 sepol_policydb_t * policydb, 1337 int usepasswd, 1338 char *ignoredirs) 1339 { 1340 genhomedircon_settings_t s; 1341 FILE *out = NULL; 1342 int retval = 0; 1343 1344 assert(sh); 1345 1346 s.homedir_template_path = 1347 semanage_path(SEMANAGE_TMP, SEMANAGE_HOMEDIR_TMPL); 1348 s.fcfilepath = 1349 semanage_path(SEMANAGE_TMP, SEMANAGE_STORE_FC_HOMEDIRS); 1350 1351 s.fallback = calloc(1, sizeof(genhomedircon_user_entry_t)); 1352 if (s.fallback == NULL) { 1353 retval = STATUS_ERR; 1354 goto done; 1355 } 1356 1357 s.fallback->name = strdup(FALLBACK_NAME); 1358 s.fallback->sename = strdup(FALLBACK_SENAME); 1359 s.fallback->prefix = strdup(FALLBACK_PREFIX); 1360 s.fallback->level = strdup(FALLBACK_LEVEL); 1361 if (s.fallback->name == NULL 1362 || s.fallback->sename == NULL 1363 || s.fallback->prefix == NULL 1364 || s.fallback->level == NULL) { 1365 retval = STATUS_ERR; 1366 goto done; 1367 } 1368 1369 if (ignoredirs) ignore_setup(ignoredirs); 1370 1371 s.usepasswd = usepasswd; 1372 s.h_semanage = sh; 1373 s.policydb = policydb; 1374 1375 if (!(out = fopen(s.fcfilepath, "w"))) { 1376 /* couldn't open output file */ 1377 ERR(sh, "Could not open the file_context file for writing"); 1378 retval = STATUS_ERR; 1379 goto done; 1380 } 1381 1382 retval = write_context_file(&s, out); 1383 1384 done: 1385 if (out != NULL) 1386 fclose(out); 1387 1388 while (s.fallback) 1389 pop_user_entry(&(s.fallback)); 1390 1391 ignore_free(); 1392 1393 return retval; 1394 } 1395