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