1 /* Author: Mark Goldman <mgoldman@tresys.com>
2 * Paul Rosenfeld <prosenfeld@tresys.com>
3 * Todd C. Miller <tmiller@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 #include <ustr.h>
38
39 #include <assert.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
52 /* paths used in get_home_dirs() */
53 #define PATH_ETC_USERADD "/etc/default/useradd"
54 #define PATH_ETC_LIBUSER "/etc/libuser.conf"
55 #define PATH_DEFAULT_HOME "/home"
56 #define PATH_EXPORT_HOME "/export/home"
57 #define PATH_ETC_LOGIN_DEFS "/etc/login.defs"
58
59 /* other paths */
60 #define PATH_SHELLS_FILE "/etc/shells"
61 #define PATH_NOLOGIN_SHELL "/sbin/nologin"
62
63 /* comments written to context file */
64 #define COMMENT_FILE_CONTEXT_HEADER "#\n#\n# " \
65 "User-specific file contexts, generated via libsemanage\n" \
66 "# use semanage command to manage system users to change" \
67 " the file_context\n#\n#\n"
68
69 #define COMMENT_USER_HOME_CONTEXT "\n\n#\n# Home Context for user %s" \
70 "\n#\n\n"
71
72 /* placeholders used in the template file
73 which are searched for and replaced */
74 #define TEMPLATE_HOME_ROOT "HOME_ROOT"
75 #define TEMPLATE_HOME_DIR "HOME_DIR"
76 #define TEMPLATE_USER "USER"
77 #define TEMPLATE_ROLE "ROLE"
78 #define TEMPLATE_SEUSER "system_u"
79 #define TEMPLATE_LEVEL "s0"
80
81 #define FALLBACK_USER "user_u"
82 #define FALLBACK_USER_PREFIX "user"
83 #define FALLBACK_USER_LEVEL "s0"
84 #define DEFAULT_LOGIN "__default__"
85
86 typedef struct {
87 const char *fcfilepath;
88 int usepasswd;
89 const char *homedir_template_path;
90 char *fallback_user;
91 char *fallback_user_prefix;
92 char *fallback_user_level;
93 semanage_handle_t *h_semanage;
94 sepol_policydb_t *policydb;
95 } genhomedircon_settings_t;
96
97 typedef struct user_entry {
98 char *name;
99 char *sename;
100 char *prefix;
101 char *home;
102 char *level;
103 struct user_entry *next;
104 } genhomedircon_user_entry_t;
105
106 typedef struct {
107 const char *search_for;
108 const char *replace_with;
109 } replacement_pair_t;
110
111 typedef struct {
112 const char *dir;
113 int matched;
114 } fc_match_handle_t;
115
116 typedef struct IgnoreDir {
117 struct IgnoreDir *next;
118 char *dir;
119 } ignoredir_t;
120
121 ignoredir_t *ignore_head = NULL;
122
ignore_free(void)123 static void ignore_free(void) {
124 ignoredir_t *next;
125
126 while (ignore_head) {
127 next = ignore_head->next;
128 free(ignore_head->dir);
129 free(ignore_head);
130 ignore_head = next;
131 }
132 }
133
ignore_setup(char * ignoredirs)134 static int ignore_setup(char *ignoredirs) {
135 char *tok;
136 ignoredir_t *ptr = NULL;
137
138 tok = strtok(ignoredirs, ";");
139 while(tok) {
140 ptr = calloc(sizeof(ignoredir_t),1);
141 if (!ptr)
142 goto err;
143 ptr->dir = strdup(tok);
144 if (!ptr->dir)
145 goto err;
146
147 ptr->next = ignore_head;
148 ignore_head = ptr;
149
150 tok = strtok(NULL, ";");
151 }
152
153 return 0;
154 err:
155 free(ptr);
156 ignore_free();
157 return -1;
158 }
159
ignore(const char * homedir)160 static int ignore(const char *homedir) {
161 ignoredir_t *ptr = ignore_head;
162 while (ptr) {
163 if (strcmp(ptr->dir, homedir) == 0) {
164 return 1;
165 }
166 ptr = ptr->next;
167 }
168 return 0;
169 }
170
default_shell_list(void)171 static semanage_list_t *default_shell_list(void)
172 {
173 semanage_list_t *list = NULL;
174
175 if (semanage_list_push(&list, "/bin/csh")
176 || semanage_list_push(&list, "/bin/tcsh")
177 || semanage_list_push(&list, "/bin/ksh")
178 || semanage_list_push(&list, "/bin/bsh")
179 || semanage_list_push(&list, "/bin/ash")
180 || semanage_list_push(&list, "/usr/bin/ksh")
181 || semanage_list_push(&list, "/usr/bin/pdksh")
182 || semanage_list_push(&list, "/bin/zsh")
183 || semanage_list_push(&list, "/bin/sh")
184 || semanage_list_push(&list, "/bin/bash"))
185 goto fail;
186
187 return list;
188
189 fail:
190 semanage_list_destroy(&list);
191 return NULL;
192 }
193
get_shell_list(void)194 static semanage_list_t *get_shell_list(void)
195 {
196 FILE *shells;
197 char *temp = NULL;
198 semanage_list_t *list = NULL;
199 size_t buff_len = 0;
200 ssize_t len;
201
202 shells = fopen(PATH_SHELLS_FILE, "r");
203 if (!shells)
204 return default_shell_list();
205 while ((len = getline(&temp, &buff_len, shells)) > 0) {
206 if (temp[len-1] == '\n') temp[len-1] = 0;
207 if (strcmp(temp, PATH_NOLOGIN_SHELL)) {
208 if (semanage_list_push(&list, temp)) {
209 free(temp);
210 semanage_list_destroy(&list);
211 return default_shell_list();
212 }
213 }
214 }
215 free(temp);
216
217 return list;
218 }
219
220 /* Helper function called via semanage_fcontext_iterate() */
fcontext_matches(const semanage_fcontext_t * fcontext,void * varg)221 static int fcontext_matches(const semanage_fcontext_t *fcontext, void *varg)
222 {
223 const char *oexpr = semanage_fcontext_get_expr(fcontext);
224 fc_match_handle_t *handp = varg;
225 struct Ustr *expr;
226 regex_t re;
227 int type, retval = -1;
228
229 /* Only match ALL or DIR */
230 type = semanage_fcontext_get_type(fcontext);
231 if (type != SEMANAGE_FCONTEXT_ALL && type != SEMANAGE_FCONTEXT_ALL)
232 return 0;
233
234 /* Convert oexpr into a Ustr and anchor it at the beginning */
235 expr = ustr_dup_cstr("^");
236 if (expr == USTR_NULL)
237 goto done;
238 if (!ustr_add_cstr(&expr, oexpr))
239 goto done;
240
241 /* Strip off trailing ".+" or ".*" */
242 if (ustr_cmp_suffix_cstr_eq(expr, ".+") ||
243 ustr_cmp_suffix_cstr_eq(expr, ".*")) {
244 if (!ustr_del(&expr, 2))
245 goto done;
246 }
247
248 /* Strip off trailing "(/.*)?" */
249 if (ustr_cmp_suffix_cstr_eq(expr, "(/.*)?")) {
250 if (!ustr_del(&expr, 6))
251 goto done;
252 }
253
254 if (ustr_cmp_suffix_cstr_eq(expr, "/")) {
255 if (!ustr_del(&expr, 1))
256 goto done;
257 }
258
259 /* Append pattern to eat up trailing slashes */
260 if (!ustr_add_cstr(&expr, "/*$"))
261 goto done;
262
263 /* Check dir against expr */
264 if (regcomp(&re, ustr_cstr(expr), REG_EXTENDED) != 0)
265 goto done;
266 if (regexec(&re, handp->dir, 0, NULL, 0) == 0)
267 handp->matched = 1;
268 regfree(&re);
269
270 retval = 0;
271
272 done:
273 ustr_free(expr);
274
275 return retval;
276 }
277
get_home_dirs(genhomedircon_settings_t * s)278 static semanage_list_t *get_home_dirs(genhomedircon_settings_t * s)
279 {
280 semanage_list_t *homedir_list = NULL;
281 semanage_list_t *shells = NULL;
282 fc_match_handle_t hand;
283 char *rbuf = NULL;
284 char *path = NULL;
285 long rbuflen;
286 uid_t temp, minuid = 500, maxuid = 60000;
287 int minuid_set = 0;
288 struct passwd pwstorage, *pwbuf;
289 struct stat buf;
290 int retval;
291
292 path = semanage_findval(PATH_ETC_USERADD, "HOME", "=");
293 if (path && *path) {
294 if (semanage_list_push(&homedir_list, path))
295 goto fail;
296 }
297 free(path);
298
299 path = semanage_findval(PATH_ETC_LIBUSER, "LU_HOMEDIRECTORY", "=");
300 if (path && *path) {
301 if (semanage_list_push(&homedir_list, path))
302 goto fail;
303 }
304 free(path);
305 path = NULL;
306
307 if (!homedir_list) {
308 if (semanage_list_push(&homedir_list, PATH_DEFAULT_HOME)) {
309 goto fail;
310 }
311 }
312
313 if (!stat(PATH_EXPORT_HOME, &buf)) {
314 if (S_ISDIR(buf.st_mode)) {
315 if (semanage_list_push(&homedir_list, PATH_EXPORT_HOME)) {
316 goto fail;
317 }
318 }
319 }
320
321 if (!(s->usepasswd))
322 return homedir_list;
323
324 shells = get_shell_list();
325 assert(shells);
326
327 path = semanage_findval(PATH_ETC_LOGIN_DEFS, "UID_MIN", NULL);
328 if (path && *path) {
329 temp = atoi(path);
330 minuid = temp;
331 minuid_set = 1;
332 }
333 free(path);
334 path = NULL;
335
336 path = semanage_findval(PATH_ETC_LOGIN_DEFS, "UID_MAX", NULL);
337 if (path && *path) {
338 temp = atoi(path);
339 maxuid = temp;
340 }
341 free(path);
342 path = NULL;
343
344 path = semanage_findval(PATH_ETC_LIBUSER, "LU_UIDNUMBER", "=");
345 if (path && *path) {
346 temp = atoi(path);
347 if (!minuid_set || temp < minuid) {
348 minuid = temp;
349 minuid_set = 1;
350 }
351 }
352 free(path);
353 path = NULL;
354
355 rbuflen = sysconf(_SC_GETPW_R_SIZE_MAX);
356 if (rbuflen <= 0)
357 goto fail;
358 rbuf = malloc(rbuflen);
359 if (rbuf == NULL)
360 goto fail;
361 setpwent();
362 while ((retval = getpwent_r(&pwstorage, rbuf, rbuflen, &pwbuf)) == 0) {
363 if (pwbuf->pw_uid < minuid || pwbuf->pw_uid > maxuid)
364 continue;
365 if (!semanage_list_find(shells, pwbuf->pw_shell))
366 continue;
367 int len = strlen(pwbuf->pw_dir) -1;
368 for(; len > 0 && pwbuf->pw_dir[len] == '/'; len--) {
369 pwbuf->pw_dir[len] = '\0';
370 }
371 if (strcmp(pwbuf->pw_dir, "/") == 0)
372 continue;
373 if (ignore(pwbuf->pw_dir))
374 continue;
375 if (semanage_str_count(pwbuf->pw_dir, '/') <= 1)
376 continue;
377 if (!(path = strdup(pwbuf->pw_dir))) {
378 break;
379 }
380
381 semanage_rtrim(path, '/');
382
383 if (!semanage_list_find(homedir_list, path)) {
384 /*
385 * Now check for an existing file context that matches
386 * so we don't label a non-homedir as a homedir.
387 */
388 hand.dir = path;
389 hand.matched = 0;
390 if (semanage_fcontext_iterate(s->h_semanage,
391 fcontext_matches, &hand) == STATUS_ERR)
392 goto fail;
393
394 /* NOTE: old genhomedircon printed a warning on match */
395 if (hand.matched) {
396 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);
397 } else {
398 if (semanage_list_push(&homedir_list, path))
399 goto fail;
400 }
401 }
402 free(path);
403 path = NULL;
404 }
405
406 if (retval && retval != ENOENT) {
407 WARN(s->h_semanage, "Error while fetching users. "
408 "Returning list so far.");
409 }
410
411 if (semanage_list_sort(&homedir_list))
412 goto fail;
413
414 endpwent();
415 free(rbuf);
416 semanage_list_destroy(&shells);
417
418 return homedir_list;
419
420 fail:
421 endpwent();
422 free(rbuf);
423 free(path);
424 semanage_list_destroy(&homedir_list);
425 semanage_list_destroy(&shells);
426 return NULL;
427 }
428
429 /**
430 * @param out the FILE to put all the output in.
431 * @return 0 on success
432 */
write_file_context_header(FILE * out)433 static int write_file_context_header(FILE * out)
434 {
435 if (fprintf(out, COMMENT_FILE_CONTEXT_HEADER) < 0) {
436 return STATUS_ERR;
437 }
438
439 return STATUS_SUCCESS;
440 }
441
442 /* Predicates for use with semanage_slurp_file_filter() the homedir_template
443 * file currently contains lines that serve as the template for a user's
444 * homedir.
445 *
446 * It also contains lines that are the template for the parent of a
447 * user's home directory.
448 *
449 * Currently, the only lines that apply to the the root of a user's home
450 * directory are all prefixed with the string "HOME_ROOT". All other
451 * lines apply to a user's home directory. If this changes the
452 * following predicates need to change to reflect that.
453 */
HOME_ROOT_PRED(const char * string)454 static int HOME_ROOT_PRED(const char *string)
455 {
456 return semanage_is_prefix(string, TEMPLATE_HOME_ROOT);
457 }
458
HOME_DIR_PRED(const char * string)459 static int HOME_DIR_PRED(const char *string)
460 {
461 return semanage_is_prefix(string, TEMPLATE_HOME_DIR);
462 }
463
USER_CONTEXT_PRED(const char * string)464 static int USER_CONTEXT_PRED(const char *string)
465 {
466 return (int)(strstr(string, TEMPLATE_USER) != NULL);
467 }
468
469 /* make_tempate
470 * @param s the settings holding the paths to various files
471 * @param pred function pointer to function to use as filter for slurp
472 * file filter
473 * @return a list of lines from the template file with inappropriate
474 * lines filtered out.
475 */
make_template(genhomedircon_settings_t * s,int (* pred)(const char *))476 static semanage_list_t *make_template(genhomedircon_settings_t * s,
477 int (*pred) (const char *))
478 {
479 FILE *template_file = NULL;
480 semanage_list_t *template_data = NULL;
481
482 template_file = fopen(s->homedir_template_path, "r");
483 if (!template_file)
484 return NULL;
485 template_data = semanage_slurp_file_filter(template_file, pred);
486 fclose(template_file);
487
488 return template_data;
489 }
490
replace_all(const char * str,const replacement_pair_t * repl)491 static Ustr *replace_all(const char *str, const replacement_pair_t * repl)
492 {
493 Ustr *retval = USTR_NULL;
494 int i;
495
496 if (!str || !repl)
497 goto done;
498 if (!(retval = ustr_dup_cstr(str)))
499 goto done;
500
501 for (i = 0; repl[i].search_for; i++) {
502 ustr_replace_cstr(&retval, repl[i].search_for,
503 repl[i].replace_with, 0);
504 }
505 if (ustr_enomem(retval))
506 ustr_sc_free(&retval);
507
508 done:
509 return retval;
510 }
511
extract_context(Ustr * line)512 static const char * extract_context(Ustr *line)
513 {
514 const char whitespace[] = " \t\n";
515 size_t off, len;
516
517 /* check for trailing whitespace */
518 off = ustr_spn_chrs_rev(line, 0, whitespace, strlen(whitespace));
519
520 /* find the length of the last field in line */
521 len = ustr_cspn_chrs_rev(line, off, whitespace, strlen(whitespace));
522
523 if (len == 0)
524 return NULL;
525 return ustr_cstr(line) + ustr_len(line) - (len + off);
526 }
527
check_line(genhomedircon_settings_t * s,Ustr * line)528 static int check_line(genhomedircon_settings_t * s, Ustr *line)
529 {
530 sepol_context_t *ctx_record = NULL;
531 const char *ctx_str;
532 int result;
533
534 ctx_str = extract_context(line);
535 if (!ctx_str)
536 return STATUS_ERR;
537
538 result = sepol_context_from_string(s->h_semanage->sepolh,
539 ctx_str, &ctx_record);
540 if (result == STATUS_SUCCESS && ctx_record != NULL) {
541 sepol_msg_set_callback(s->h_semanage->sepolh, NULL, NULL);
542 result = sepol_context_check(s->h_semanage->sepolh,
543 s->policydb, ctx_record);
544 sepol_msg_set_callback(s->h_semanage->sepolh,
545 semanage_msg_relay_handler, s->h_semanage);
546 sepol_context_free(ctx_record);
547 }
548 return result;
549 }
550
write_home_dir_context(genhomedircon_settings_t * s,FILE * out,semanage_list_t * tpl,const char * user,const char * seuser,const char * home,const char * role_prefix,const char * level)551 static int write_home_dir_context(genhomedircon_settings_t * s, FILE * out,
552 semanage_list_t * tpl, const char *user,
553 const char *seuser, const char *home,
554 const char *role_prefix, const char *level)
555 {
556 replacement_pair_t repl[] = {
557 {.search_for = TEMPLATE_SEUSER,.replace_with = seuser},
558 {.search_for = TEMPLATE_HOME_DIR,.replace_with = home},
559 {.search_for = TEMPLATE_ROLE,.replace_with = role_prefix},
560 {.search_for = TEMPLATE_LEVEL,.replace_with = level},
561 {NULL, NULL}
562 };
563 Ustr *line = USTR_NULL;
564
565 if (fprintf(out, COMMENT_USER_HOME_CONTEXT, user) < 0)
566 return STATUS_ERR;
567
568 for (; tpl; tpl = tpl->next) {
569 line = replace_all(tpl->data, repl);
570 if (!line)
571 goto fail;
572 if (check_line(s, line) == STATUS_SUCCESS) {
573 if (!ustr_io_putfileline(&line, out))
574 goto fail;
575 }
576 ustr_sc_free(&line);
577 }
578 return STATUS_SUCCESS;
579
580 fail:
581 ustr_sc_free(&line);
582 return STATUS_ERR;
583 }
584
write_home_root_context(genhomedircon_settings_t * s,FILE * out,semanage_list_t * tpl,char * homedir)585 static int write_home_root_context(genhomedircon_settings_t * s, FILE * out,
586 semanage_list_t * tpl, char *homedir)
587 {
588 replacement_pair_t repl[] = {
589 {.search_for = TEMPLATE_HOME_ROOT,.replace_with = homedir},
590 {NULL, NULL}
591 };
592 Ustr *line = USTR_NULL;
593
594 for (; tpl; tpl = tpl->next) {
595 line = replace_all(tpl->data, repl);
596 if (!line)
597 goto fail;
598 if (check_line(s, line) == STATUS_SUCCESS) {
599 if (!ustr_io_putfileline(&line, out))
600 goto fail;
601 }
602 ustr_sc_free(&line);
603 }
604 return STATUS_SUCCESS;
605
606 fail:
607 ustr_sc_free(&line);
608 return STATUS_ERR;
609 }
610
write_user_context(genhomedircon_settings_t * s,FILE * out,semanage_list_t * tpl,const char * user,const char * seuser,const char * role_prefix)611 static int write_user_context(genhomedircon_settings_t * s, FILE * out,
612 semanage_list_t * tpl, const char *user,
613 const char *seuser, const char *role_prefix)
614 {
615 replacement_pair_t repl[] = {
616 {.search_for = TEMPLATE_USER,.replace_with = user},
617 {.search_for = TEMPLATE_ROLE,.replace_with = role_prefix},
618 {.search_for = TEMPLATE_SEUSER,.replace_with = seuser},
619 {NULL, NULL}
620 };
621 Ustr *line = USTR_NULL;
622
623 for (; tpl; tpl = tpl->next) {
624 line = replace_all(tpl->data, repl);
625 if (!line)
626 goto fail;
627 if (check_line(s, line) == STATUS_SUCCESS) {
628 if (!ustr_io_putfileline(&line, out))
629 goto fail;
630 }
631 ustr_sc_free(&line);
632 }
633 return STATUS_SUCCESS;
634
635 fail:
636 ustr_sc_free(&line);
637 return STATUS_ERR;
638 }
639
user_sort_func(semanage_user_t ** arg1,semanage_user_t ** arg2)640 static int user_sort_func(semanage_user_t ** arg1, semanage_user_t ** arg2)
641 {
642 return strcmp(semanage_user_get_name(*arg1),
643 semanage_user_get_name(*arg2));
644 }
645
name_user_cmp(char * key,semanage_user_t ** val)646 static int name_user_cmp(char *key, semanage_user_t ** val)
647 {
648 return strcmp(key, semanage_user_get_name(*val));
649 }
650
push_user_entry(genhomedircon_user_entry_t ** list,const char * n,const char * sen,const char * pre,const char * h,const char * l)651 static int push_user_entry(genhomedircon_user_entry_t ** list, const char *n,
652 const char *sen, const char *pre, const char *h,
653 const char *l)
654 {
655 genhomedircon_user_entry_t *temp = NULL;
656 char *name = NULL;
657 char *sename = NULL;
658 char *prefix = NULL;
659 char *home = NULL;
660 char *level = NULL;
661
662 temp = malloc(sizeof(genhomedircon_user_entry_t));
663 if (!temp)
664 goto cleanup;
665 name = strdup(n);
666 if (!name)
667 goto cleanup;
668 sename = strdup(sen);
669 if (!sename)
670 goto cleanup;
671 prefix = strdup(pre);
672 if (!prefix)
673 goto cleanup;
674 home = strdup(h);
675 if (!home)
676 goto cleanup;
677 level = strdup(l);
678 if (!level)
679 goto cleanup;
680
681 temp->name = name;
682 temp->sename = sename;
683 temp->prefix = prefix;
684 temp->home = home;
685 temp->level = level;
686 temp->next = (*list);
687 (*list) = temp;
688
689 return STATUS_SUCCESS;
690
691 cleanup:
692 free(name);
693 free(sename);
694 free(prefix);
695 free(home);
696 free(level);
697 free(temp);
698 return STATUS_ERR;
699 }
700
pop_user_entry(genhomedircon_user_entry_t ** list)701 static void pop_user_entry(genhomedircon_user_entry_t ** list)
702 {
703 genhomedircon_user_entry_t *temp;
704
705 if (!list || !(*list))
706 return;
707
708 temp = *list;
709 *list = temp->next;
710 free(temp->name);
711 free(temp->sename);
712 free(temp->prefix);
713 free(temp->home);
714 free(temp->level);
715 free(temp);
716 }
717
set_fallback_user(genhomedircon_settings_t * s,const char * user,const char * prefix,const char * level)718 static int set_fallback_user(genhomedircon_settings_t *s, const char *user,
719 const char *prefix, const char *level)
720 {
721 char *fallback_user = strdup(user);
722 char *fallback_user_prefix = strdup(prefix);
723 char *fallback_user_level = NULL;
724 if (level)
725 fallback_user_level = strdup(level);
726
727 if (fallback_user == NULL || fallback_user_prefix == NULL ||
728 (fallback_user_level == NULL && level != NULL)) {
729 free(fallback_user);
730 free(fallback_user_prefix);
731 free(fallback_user_level);
732 return STATUS_ERR;
733 }
734
735 free(s->fallback_user);
736 free(s->fallback_user_prefix);
737 free(s->fallback_user_level);
738 s->fallback_user = fallback_user;
739 s->fallback_user_prefix = fallback_user_prefix;
740 s->fallback_user_level = fallback_user_level;
741 return STATUS_SUCCESS;
742 }
743
setup_fallback_user(genhomedircon_settings_t * s)744 static int setup_fallback_user(genhomedircon_settings_t * s)
745 {
746 semanage_seuser_t **seuser_list = NULL;
747 unsigned int nseusers = 0;
748 semanage_user_key_t *key = NULL;
749 semanage_user_t *u = NULL;
750 const char *name = NULL;
751 const char *seuname = NULL;
752 const char *prefix = NULL;
753 const char *level = NULL;
754 unsigned int i;
755 int retval;
756 int errors = 0;
757
758 retval = semanage_seuser_list(s->h_semanage, &seuser_list, &nseusers);
759 if (retval < 0 || (nseusers < 1)) {
760 /* if there are no users, this function can't do any other work */
761 return errors;
762 }
763
764 for (i = 0; i < nseusers; i++) {
765 name = semanage_seuser_get_name(seuser_list[i]);
766 if (strcmp(name, DEFAULT_LOGIN) == 0) {
767 seuname = semanage_seuser_get_sename(seuser_list[i]);
768
769 /* find the user structure given the name */
770 if (semanage_user_key_create(s->h_semanage, seuname,
771 &key) < 0) {
772 errors = STATUS_ERR;
773 break;
774 }
775 if (semanage_user_query(s->h_semanage, key, &u) < 0)
776 {
777 prefix = name;
778 level = FALLBACK_USER_LEVEL;
779 }
780 else
781 {
782 prefix = semanage_user_get_prefix(u);
783 level = semanage_user_get_mlslevel(u);
784 if (!level)
785 level = FALLBACK_USER_LEVEL;
786 }
787
788 if (set_fallback_user(s, seuname, prefix, level) != 0)
789 errors = STATUS_ERR;
790 semanage_user_key_free(key);
791 if (u)
792 semanage_user_free(u);
793 break;
794 }
795 }
796
797 for (i = 0; i < nseusers; i++)
798 semanage_seuser_free(seuser_list[i]);
799 free(seuser_list);
800
801 return errors;
802 }
803
get_users(genhomedircon_settings_t * s,int * errors)804 static genhomedircon_user_entry_t *get_users(genhomedircon_settings_t * s,
805 int *errors)
806 {
807 genhomedircon_user_entry_t *head = NULL;
808 semanage_seuser_t **seuser_list = NULL;
809 unsigned int nseusers = 0;
810 semanage_user_t **user_list = NULL;
811 unsigned int nusers = 0;
812 semanage_user_t **u = NULL;
813 const char *name = NULL;
814 const char *seuname = NULL;
815 const char *prefix = NULL;
816 const char *level = NULL;
817 struct passwd pwstorage, *pwent = NULL;
818 unsigned int i;
819 long rbuflen;
820 char *rbuf = NULL;
821 int retval;
822
823 *errors = 0;
824 retval = semanage_seuser_list(s->h_semanage, &seuser_list, &nseusers);
825 if (retval < 0 || (nseusers < 1)) {
826 /* if there are no users, this function can't do any other work */
827 return NULL;
828 }
829
830 if (semanage_user_list(s->h_semanage, &user_list, &nusers) < 0) {
831 nusers = 0;
832 }
833
834 qsort(user_list, nusers, sizeof(semanage_user_t *),
835 (int (*)(const void *, const void *))&user_sort_func);
836
837 /* Allocate space for the getpwnam_r buffer */
838 rbuflen = sysconf(_SC_GETPW_R_SIZE_MAX);
839 if (rbuflen <= 0)
840 goto cleanup;
841 rbuf = malloc(rbuflen);
842 if (rbuf == NULL)
843 goto cleanup;
844
845 for (i = 0; i < nseusers; i++) {
846 seuname = semanage_seuser_get_sename(seuser_list[i]);
847 name = semanage_seuser_get_name(seuser_list[i]);
848
849 if (strcmp(name,"root") && strcmp(seuname, s->fallback_user) == 0)
850 continue;
851
852 if (strcmp(name, DEFAULT_LOGIN) == 0)
853 continue;
854
855 if (strcmp(name, TEMPLATE_SEUSER) == 0)
856 continue;
857
858 /* %groupname syntax */
859 if (name[0] == '%')
860 continue;
861
862 /* find the user structure given the name */
863 u = bsearch(seuname, user_list, nusers, sizeof(semanage_user_t *),
864 (int (*)(const void *, const void *))
865 &name_user_cmp);
866 if (u) {
867 prefix = semanage_user_get_prefix(*u);
868 level = semanage_user_get_mlslevel(*u);
869 if (!level)
870 level = FALLBACK_USER_LEVEL;
871 } else {
872 prefix = name;
873 level = FALLBACK_USER_LEVEL;
874 }
875
876 retval = getpwnam_r(name, &pwstorage, rbuf, rbuflen, &pwent);
877 if (retval != 0 || pwent == NULL) {
878 if (retval != 0 && retval != ENOENT) {
879 *errors = STATUS_ERR;
880 goto cleanup;
881 }
882
883 WARN(s->h_semanage,
884 "user %s not in password file", name);
885 continue;
886 }
887
888 int len = strlen(pwent->pw_dir) -1;
889 for(; len > 0 && pwent->pw_dir[len] == '/'; len--) {
890 pwent->pw_dir[len] = '\0';
891 }
892
893 if (strcmp(pwent->pw_dir, "/") == 0) {
894 /* don't relabel / genhomdircon checked to see if root
895 * was the user and if so, set his home directory to
896 * /root */
897 continue;
898 }
899 if (ignore(pwent->pw_dir))
900 continue;
901 if (push_user_entry(&head, name, seuname,
902 prefix, pwent->pw_dir, level) != STATUS_SUCCESS) {
903 *errors = STATUS_ERR;
904 break;
905 }
906 }
907
908 cleanup:
909 free(rbuf);
910 if (*errors) {
911 for (; head; pop_user_entry(&head)) {
912 /* the pop function takes care of all the cleanup
913 so the loop body is just empty */
914 }
915 }
916 for (i = 0; i < nseusers; i++) {
917 semanage_seuser_free(seuser_list[i]);
918 }
919 free(seuser_list);
920
921 for (i = 0; i < nusers; i++) {
922 semanage_user_free(user_list[i]);
923 }
924 free(user_list);
925
926 return head;
927 }
928
write_gen_home_dir_context(genhomedircon_settings_t * s,FILE * out,semanage_list_t * user_context_tpl,semanage_list_t * homedir_context_tpl)929 static int write_gen_home_dir_context(genhomedircon_settings_t * s, FILE * out,
930 semanage_list_t * user_context_tpl,
931 semanage_list_t * homedir_context_tpl)
932 {
933 genhomedircon_user_entry_t *users;
934 int errors = 0;
935
936 users = get_users(s, &errors);
937 if (!users && errors) {
938 return STATUS_ERR;
939 }
940
941 for (; users; pop_user_entry(&users)) {
942 if (write_home_dir_context(s, out, homedir_context_tpl,
943 users->name,
944 users->sename, users->home,
945 users->prefix, users->level))
946 goto err;
947 if (write_user_context(s, out, user_context_tpl, users->name,
948 users->sename, users->prefix))
949 goto err;
950 }
951
952 return STATUS_SUCCESS;
953 err:
954 for (; users; pop_user_entry(&users)) {
955 /* the pop function takes care of all the cleanup
956 * so the loop body is just empty */
957 }
958
959 return STATUS_ERR;
960 }
961
962 /**
963 * @param s settings structure, stores various paths etc. Must never be NULL
964 * @param out the FILE to put all the output in.
965 * @return 0 on success
966 */
write_context_file(genhomedircon_settings_t * s,FILE * out)967 static int write_context_file(genhomedircon_settings_t * s, FILE * out)
968 {
969 semanage_list_t *homedirs = NULL;
970 semanage_list_t *h = NULL;
971 semanage_list_t *user_context_tpl = NULL;
972 semanage_list_t *homedir_context_tpl = NULL;
973 semanage_list_t *homeroot_context_tpl = NULL;
974 int retval = STATUS_SUCCESS;
975
976 homedir_context_tpl = make_template(s, &HOME_DIR_PRED);
977 homeroot_context_tpl = make_template(s, &HOME_ROOT_PRED);
978 user_context_tpl = make_template(s, &USER_CONTEXT_PRED);
979
980 if (!homedir_context_tpl && !homeroot_context_tpl && !user_context_tpl)
981 goto done;
982
983 if (write_file_context_header(out) != STATUS_SUCCESS) {
984 retval = STATUS_ERR;
985 goto done;
986 }
987
988 if (setup_fallback_user(s) != 0) {
989 retval = STATUS_ERR;
990 goto done;
991 }
992
993 if (homedir_context_tpl || homeroot_context_tpl) {
994 homedirs = get_home_dirs(s);
995 if (!homedirs) {
996 WARN(s->h_semanage,
997 "no home directories were available, exiting without writing");
998 goto done;
999 }
1000
1001 for (h = homedirs; h; h = h->next) {
1002 Ustr *temp = ustr_dup_cstr(h->data);
1003
1004 if (!temp || !ustr_add_cstr(&temp, "/[^/]*")) {
1005 ustr_sc_free(&temp);
1006 retval = STATUS_ERR;
1007 goto done;
1008 }
1009
1010 if (write_home_dir_context(s, out,
1011 homedir_context_tpl,
1012 s->fallback_user, s->fallback_user,
1013 ustr_cstr(temp),
1014 s->fallback_user_prefix, s->fallback_user_level) !=
1015 STATUS_SUCCESS) {
1016 ustr_sc_free(&temp);
1017 retval = STATUS_ERR;
1018 goto done;
1019 }
1020 if (write_home_root_context(s, out,
1021 homeroot_context_tpl,
1022 h->data) != STATUS_SUCCESS) {
1023 ustr_sc_free(&temp);
1024 retval = STATUS_ERR;
1025 goto done;
1026 }
1027
1028 ustr_sc_free(&temp);
1029 }
1030 }
1031 if (user_context_tpl) {
1032 if (write_user_context(s, out, user_context_tpl,
1033 ".*", s->fallback_user,
1034 s->fallback_user_prefix) != STATUS_SUCCESS) {
1035 retval = STATUS_ERR;
1036 goto done;
1037 }
1038
1039 if (write_gen_home_dir_context(s, out, user_context_tpl,
1040 homedir_context_tpl) != STATUS_SUCCESS) {
1041 retval = STATUS_ERR;
1042 }
1043 }
1044
1045 done:
1046 /* Cleanup */
1047 semanage_list_destroy(&homedirs);
1048 semanage_list_destroy(&user_context_tpl);
1049 semanage_list_destroy(&homedir_context_tpl);
1050 semanage_list_destroy(&homeroot_context_tpl);
1051
1052 return retval;
1053 }
1054
semanage_genhomedircon(semanage_handle_t * sh,sepol_policydb_t * policydb,int usepasswd,char * ignoredirs)1055 int semanage_genhomedircon(semanage_handle_t * sh,
1056 sepol_policydb_t * policydb,
1057 int usepasswd,
1058 char *ignoredirs)
1059 {
1060 genhomedircon_settings_t s;
1061 FILE *out = NULL;
1062 int retval = 0;
1063
1064 assert(sh);
1065
1066 s.homedir_template_path =
1067 semanage_path(SEMANAGE_TMP, SEMANAGE_HOMEDIR_TMPL);
1068 s.fcfilepath = semanage_final_path(SEMANAGE_FINAL_TMP,
1069 SEMANAGE_FC_HOMEDIRS);
1070
1071 s.fallback_user = strdup(FALLBACK_USER);
1072 s.fallback_user_prefix = strdup(FALLBACK_USER_PREFIX);
1073 s.fallback_user_level = strdup(FALLBACK_USER_LEVEL);
1074 if (s.fallback_user == NULL || s.fallback_user_prefix == NULL || s.fallback_user_level == NULL) {
1075 retval = STATUS_ERR;
1076 goto done;
1077 }
1078
1079 if (ignoredirs) ignore_setup(ignoredirs);
1080
1081 s.usepasswd = usepasswd;
1082 s.h_semanage = sh;
1083 s.policydb = policydb;
1084
1085 if (!(out = fopen(s.fcfilepath, "w"))) {
1086 /* couldn't open output file */
1087 ERR(sh, "Could not open the file_context file for writing");
1088 retval = STATUS_ERR;
1089 goto done;
1090 }
1091
1092 retval = write_context_file(&s, out);
1093
1094 done:
1095 if (out != NULL)
1096 fclose(out);
1097
1098 free(s.fallback_user);
1099 free(s.fallback_user_prefix);
1100 free(s.fallback_user_level);
1101 ignore_free();
1102
1103 return retval;
1104 }
1105