]>
Commit | Line | Data |
---|---|---|
c82d9c97 KZ |
1 | /* |
2 | * Copyright (C) 2003, 2004, 2005 Thorsten Kukuk | |
3 | * Author: Thorsten Kukuk <kukuk@suse.de> | |
4 | * | |
5 | * Redistribution and use in source and binary forms, with or without | |
6 | * modification, are permitted provided that the following conditions | |
7 | * are met: | |
8 | * | |
9 | * 1. Redistributions of source code must retain any existing copyright | |
10 | * notice, and this entire permission notice in its entirety, | |
11 | * including the disclaimer of warranties. | |
12 | * | |
13 | * 2. Redistributions in binary form must reproduce all prior and current | |
14 | * copyright notices, this list of conditions, and the following | |
15 | * disclaimer in the documentation and/or other materials provided | |
16 | * with the distribution. | |
17 | * | |
18 | * 3. The name of any author may not be used to endorse or promote | |
19 | * products derived from this software without their specific prior | |
20 | * written permission. | |
21 | */ | |
3f9c237d | 22 | #include <assert.h> |
c82d9c97 | 23 | #include <ctype.h> |
3f9c237d SK |
24 | #include <errno.h> |
25 | #include <limits.h> | |
c82d9c97 KZ |
26 | #include <stdio.h> |
27 | #include <stdlib.h> | |
28 | #include <string.h> | |
c82d9c97 | 29 | #include <sys/syslog.h> |
78dd7450 OO |
30 | #include <sys/stat.h> |
31 | #include <sys/types.h> | |
32 | #include <pwd.h> | |
c82d9c97 KZ |
33 | |
34 | #include "c.h" | |
439cdf1e | 35 | #include "closestream.h" |
3f9c237d | 36 | #include "logindefs.h" |
c82d9c97 | 37 | #include "nls.h" |
c82d9c97 | 38 | #include "pathnames.h" |
3f9c237d | 39 | #include "xalloc.h" |
c82d9c97 | 40 | |
9e584ff3 TK |
41 | |
42 | static void (*logindefs_loader)(void *) = NULL; | |
43 | static void *logindefs_loader_data = NULL; | |
44 | ||
45 | void logindefs_set_loader(void (*loader)(void *data), void *data) | |
46 | { | |
47 | logindefs_loader = loader; | |
48 | logindefs_loader_data = data; | |
49 | } | |
50 | ||
51 | #ifndef HAVE_LIBECONF | |
52 | ||
c82d9c97 KZ |
53 | struct item { |
54 | char *name; /* name of the option. */ | |
55 | char *value; /* value of the option. */ | |
56 | char *path; /* name of config file for this option. */ | |
57 | ||
58 | struct item *next; /* pointer to next option. */ | |
59 | }; | |
60 | ||
61 | static struct item *list = NULL; | |
62 | ||
63 | void free_getlogindefs_data(void) | |
64 | { | |
65 | struct item *ptr; | |
66 | ||
67 | ptr = list; | |
68 | while (ptr) { | |
69 | struct item *tmp = ptr->next; | |
70 | ||
71 | free(ptr->path); | |
72 | free(ptr->name); | |
73 | free(ptr->value); | |
74 | free(ptr); | |
75 | ptr = tmp; | |
76 | } | |
77 | ||
78 | list = NULL; | |
79 | } | |
80 | ||
81 | static void store(const char *name, const char *value, const char *path) | |
82 | { | |
83 | struct item *new = xmalloc(sizeof(struct item)); | |
84 | ||
85 | if (!name) | |
86 | abort(); | |
87 | ||
88 | new->name = xstrdup(name); | |
89 | new->value = value && *value ? xstrdup(value) : NULL; | |
90 | new->path = xstrdup(path); | |
91 | new->next = list; | |
92 | list = new; | |
93 | } | |
94 | ||
9c44ac50 | 95 | void logindefs_load_file(const char *filename) |
c82d9c97 KZ |
96 | { |
97 | FILE *f; | |
98 | char buf[BUFSIZ]; | |
99 | ||
100 | f = fopen(filename, "r"); | |
101 | if (!f) | |
102 | return; | |
103 | ||
104 | while (fgets(buf, sizeof(buf), f)) { | |
105 | ||
106 | char *p, *name, *data = NULL; | |
107 | ||
108 | if (*buf == '#' || *buf == '\n') | |
3f9c237d | 109 | continue; /* only comment or empty line */ |
c82d9c97 KZ |
110 | |
111 | p = strchr(buf, '#'); | |
112 | if (p) | |
113 | *p = '\0'; | |
114 | else { | |
115 | size_t n = strlen(buf); | |
116 | if (n && *(buf + n - 1) == '\n') | |
117 | *(buf + n - 1) = '\0'; | |
118 | } | |
119 | ||
120 | if (!*buf) | |
3f9c237d | 121 | continue; /* empty line */ |
c82d9c97 KZ |
122 | |
123 | /* ignore space at begin of the line */ | |
124 | name = buf; | |
3f9c237d | 125 | while (*name && isspace((unsigned)*name)) |
c82d9c97 KZ |
126 | name++; |
127 | ||
128 | /* go to the end of the name */ | |
129 | data = name; | |
3f9c237d | 130 | while (*data && !(isspace((unsigned)*data) || *data == '=')) |
c82d9c97 KZ |
131 | data++; |
132 | if (data > name && *data) | |
133 | *data++ = '\0'; | |
134 | ||
135 | if (!*name || data == name) | |
136 | continue; | |
137 | ||
138 | /* go to the begin of the value */ | |
3f9c237d SK |
139 | while (*data |
140 | && (isspace((unsigned)*data) || *data == '=' | |
141 | || *data == '"')) | |
142 | data++; | |
c82d9c97 KZ |
143 | |
144 | /* remove space at the end of the value */ | |
145 | p = data + strlen(data); | |
146 | if (p > data) | |
147 | p--; | |
3f9c237d | 148 | while (p > data && (isspace((unsigned)*p) || *p == '"')) |
c82d9c97 KZ |
149 | *p-- = '\0'; |
150 | ||
151 | store(name, data, filename); | |
152 | } | |
153 | ||
154 | fclose(f); | |
155 | } | |
156 | ||
4082ab2c | 157 | static void load_defaults(void) |
9c44ac50 | 158 | { |
832f5cd5 KZ |
159 | if (logindefs_loader) |
160 | logindefs_loader(logindefs_loader_data); | |
9c44ac50 LN |
161 | else |
162 | logindefs_load_file(_PATH_LOGINDEFS); | |
163 | } | |
164 | ||
c82d9c97 KZ |
165 | static struct item *search(const char *name) |
166 | { | |
167 | struct item *ptr; | |
168 | ||
169 | if (!list) | |
9c44ac50 | 170 | load_defaults(); |
c82d9c97 KZ |
171 | |
172 | ptr = list; | |
173 | while (ptr != NULL) { | |
174 | if (strcasecmp(name, ptr->name) == 0) | |
175 | return ptr; | |
176 | ptr = ptr->next; | |
177 | } | |
178 | ||
179 | return NULL; | |
180 | } | |
181 | ||
182 | static const char *search_config(const char *name) | |
183 | { | |
184 | struct item *ptr; | |
185 | ||
186 | ptr = list; | |
187 | while (ptr != NULL) { | |
188 | if (strcasecmp(name, ptr->name) == 0) | |
189 | return ptr->path; | |
190 | ptr = ptr->next; | |
191 | } | |
192 | ||
193 | return NULL; | |
194 | } | |
195 | ||
196 | int getlogindefs_bool(const char *name, int dflt) | |
197 | { | |
3f9c237d | 198 | struct item *ptr = search(name); |
c82d9c97 KZ |
199 | return ptr && ptr->value ? (strcasecmp(ptr->value, "yes") == 0) : dflt; |
200 | } | |
201 | ||
62342745 | 202 | unsigned long getlogindefs_num(const char *name, unsigned long dflt) |
c82d9c97 KZ |
203 | { |
204 | struct item *ptr = search(name); | |
205 | char *end = NULL; | |
c9baf5da | 206 | unsigned long retval; |
c82d9c97 KZ |
207 | |
208 | if (!ptr || !ptr->value) | |
209 | return dflt; | |
210 | ||
211 | errno = 0; | |
c9baf5da | 212 | retval = strtoul(ptr->value, &end, 0); |
c82d9c97 KZ |
213 | if (end && *end == '\0' && !errno) |
214 | return retval; | |
215 | ||
216 | syslog(LOG_NOTICE, _("%s: %s contains invalid numerical value: %s"), | |
3f9c237d | 217 | search_config(name), name, ptr->value); |
c82d9c97 KZ |
218 | return dflt; |
219 | } | |
220 | ||
221 | /* | |
222 | * Returns: | |
223 | * @dflt if @name not found | |
224 | * "" (empty string) if found, but value not defined | |
225 | * "string" if found | |
226 | */ | |
227 | const char *getlogindefs_str(const char *name, const char *dflt) | |
228 | { | |
229 | struct item *ptr = search(name); | |
230 | ||
231 | if (!ptr) | |
232 | return dflt; | |
233 | if (!ptr->value) | |
234 | return ""; | |
235 | return ptr->value; | |
236 | } | |
237 | ||
fee99106 | 238 | #else /* !HAVE_LIBECONF */ |
9e584ff3 TK |
239 | |
240 | #include <libeconf.h> | |
241 | ||
242 | static econf_file *file = NULL; | |
243 | ||
244 | void free_getlogindefs_data(void) | |
245 | { | |
246 | econf_free (file); | |
247 | file = NULL; | |
248 | } | |
249 | ||
9e584ff3 TK |
250 | static void load_defaults(void) |
251 | { | |
252 | econf_err error; | |
253 | ||
254 | if (file != NULL) | |
255 | free_getlogindefs_data(); | |
256 | ||
fee99106 KZ |
257 | error = econf_readDirs(&file, |
258 | #if USE_VENDORDIR | |
259 | _PATH_VENDORDIR, | |
260 | #else | |
261 | NULL, | |
262 | #endif | |
263 | "/etc", "login", "defs", "= \t", "#"); | |
264 | ||
265 | if (error) | |
9e584ff3 TK |
266 | syslog(LOG_NOTICE, _("Error reading login.defs: %s"), |
267 | econf_errString(error)); | |
268 | ||
269 | if (logindefs_loader) | |
270 | logindefs_loader(logindefs_loader_data); | |
271 | ||
272 | } | |
273 | ||
274 | void logindefs_load_file(const char *filename) | |
275 | { | |
b6b7348e | 276 | econf_file *file_l = NULL, *file_m = NULL; |
9e584ff3 TK |
277 | char *path; |
278 | ||
279 | logindefs_loader = NULL; /* No recursion */ | |
280 | ||
fee99106 | 281 | #if USE_VENDORDIR |
19a35394 KZ |
282 | xasprintf(&path, _PATH_VENDORDIR"/%s", filename); |
283 | ||
9e584ff3 TK |
284 | if (!econf_readFile(&file_l, path, "= \t", "#")) { |
285 | if (file == NULL) | |
286 | file = file_l; | |
287 | else if (!econf_mergeFiles(&file_m, file, file_l)) { | |
288 | econf_free(file); | |
289 | file = file_m; | |
290 | econf_free(file_l); | |
291 | } | |
292 | } | |
293 | free (path); | |
294 | #endif | |
295 | ||
19a35394 KZ |
296 | xasprintf(&path, "/etc/%s", filename); |
297 | ||
9e584ff3 TK |
298 | if (!econf_readFile(&file_l, path, "= \t", "#")) { |
299 | if (file == NULL) | |
300 | file = file_l; | |
301 | else if (!econf_mergeFiles(&file_m, file, file_l)) { | |
302 | econf_free(file); | |
303 | file = file_m; | |
304 | econf_free(file_l); | |
305 | } | |
19a35394 KZ |
306 | |
307 | /* Try original filename, could be relative */ | |
308 | } else if (!econf_readFile(&file_l, filename, "= \t", "#")) { | |
309 | if (file == NULL) | |
310 | file = file_l; | |
311 | else if (!econf_mergeFiles(&file_m, file, file_l)) { | |
312 | econf_free(file); | |
313 | file = file_m; | |
314 | econf_free(file_l); | |
9e584ff3 TK |
315 | } |
316 | } | |
317 | free (path); | |
318 | } | |
319 | ||
320 | int getlogindefs_bool(const char *name, int dflt) | |
321 | { | |
322 | bool value; | |
323 | econf_err error; | |
324 | ||
325 | if (!file) | |
326 | load_defaults(); | |
327 | ||
328 | if (!file) | |
329 | return dflt; | |
330 | ||
331 | if ((error = econf_getBoolValue(file, NULL, name, &value))) { | |
332 | if (error != ECONF_NOKEY) | |
333 | syslog(LOG_NOTICE, _("couldn't fetch %s: %s"), name, | |
334 | econf_errString(error)); | |
335 | return dflt; | |
336 | } | |
b6b7348e | 337 | return value; |
9e584ff3 TK |
338 | } |
339 | ||
340 | unsigned long getlogindefs_num(const char *name, unsigned long dflt) | |
341 | { | |
b6b7348e | 342 | uint64_t value; |
9e584ff3 TK |
343 | econf_err error; |
344 | ||
345 | if (!file) | |
346 | load_defaults(); | |
347 | ||
348 | if (!file) | |
349 | return dflt; | |
350 | ||
351 | if ((error = econf_getUInt64Value(file, NULL, name, &value))) { | |
352 | if (error != ECONF_NOKEY) | |
353 | syslog(LOG_NOTICE, _("couldn't fetch %s: %s"), name, | |
354 | econf_errString(error)); | |
355 | return dflt; | |
356 | } | |
357 | return value; | |
358 | } | |
359 | ||
360 | /* | |
361 | * Returns: | |
362 | * @dflt if @name not found | |
363 | * "" (empty string) if found, but value not defined | |
364 | * "string" if found | |
365 | */ | |
366 | const char *getlogindefs_str(const char *name, const char *dflt) | |
367 | { | |
368 | char *value; | |
369 | econf_err error; | |
370 | ||
371 | if (!file) | |
372 | load_defaults(); | |
373 | ||
374 | if (!file) | |
375 | return dflt; | |
376 | ||
377 | if ((error = econf_getStringValue(file, NULL, name, &value))) { | |
378 | if (error != ECONF_NOKEY) | |
379 | syslog(LOG_NOTICE, _("couldn't fetch %s: %s"), name, | |
380 | econf_errString(error)); | |
381 | return dflt; | |
382 | } | |
b6b7348e TK |
383 | if (value) |
384 | return value; | |
19a35394 KZ |
385 | |
386 | return xstrdup(""); | |
9e584ff3 TK |
387 | } |
388 | #endif /* !HAVE_LIBECONF */ | |
389 | ||
607e6b7c | 390 | /* |
a8077509 | 391 | * For compatibility with shadow-utils we have to support additional |
607e6b7c KZ |
392 | * syntax for environment variables in login.defs(5) file. The standard |
393 | * syntax is: | |
394 | * | |
395 | * ENV_FOO data | |
396 | * | |
397 | * but shadow-utils supports also | |
398 | * | |
399 | * ENV_FOO FOO=data | |
400 | * | |
401 | * the FOO= prefix has to be remove before we call setenv(). | |
402 | */ | |
403 | int logindefs_setenv(const char *name, const char *conf, const char *dflt) | |
404 | { | |
405 | const char *val = getlogindefs_str(conf, dflt); | |
406 | const char *p; | |
407 | ||
408 | if (!val) | |
409 | return -1; | |
410 | ||
411 | p = strchr(val, '='); | |
412 | if (p) { | |
413 | size_t sz = strlen(name); | |
414 | ||
415 | if (strncmp(val, name, sz) == 0 && *(p + 1)) { | |
416 | val = p + 1; | |
417 | if (*val == '"') | |
418 | val++; | |
419 | if (!*val) | |
420 | val = dflt; | |
421 | } | |
422 | } | |
423 | ||
424 | return val ? setenv(name, val, 1) : -1; | |
425 | } | |
426 | ||
78dd7450 OO |
427 | /* |
428 | * We need to check the effective UID/GID. For example, $HOME could be on a | |
429 | * root-squashed NFS or on an NFS with UID mapping, and access(2) uses the | |
430 | * real UID/GID. Then open(2) seems as the surest solution. | |
431 | * -- kzak@redhat.com (10-Apr-2009) | |
432 | */ | |
433 | int effective_access(const char *path, int mode) | |
434 | { | |
435 | int fd = open(path, mode); | |
436 | if (fd != -1) | |
437 | close(fd); | |
438 | return fd == -1 ? -1 : 0; | |
439 | } | |
440 | ||
78dd7450 OO |
441 | /* |
442 | * Check the per-account or the global hush-login setting. | |
443 | * | |
444 | * Hushed mode is enabled: | |
445 | * | |
446 | * a) if a global (e.g. /etc/hushlogins) hush file exists: | |
447 | * 1) for ALL ACCOUNTS if the file is empty | |
448 | * 2) for the current user if the username or shell is found in the file | |
449 | * | |
450 | * b) if a ~/.hushlogin file exists | |
451 | * | |
452 | * The ~/.hushlogin file is ignored if the global hush file exists. | |
453 | * | |
454 | * The HUSHLOGIN_FILE login.def variable overrides the default hush filename. | |
455 | * | |
456 | * Note that shadow-utils login(1) does not support "a1)". The "a1)" is | |
457 | * necessary if you want to use PAM for "Last login" message. | |
458 | * | |
459 | * -- Karel Zak <kzak@redhat.com> (26-Aug-2011) | |
460 | * | |
461 | * | |
462 | * The per-account check requires some explanation: As root we may not be able | |
463 | * to read the directory of the user if it is on an NFS-mounted filesystem. We | |
464 | * temporarily set our effective uid to the user-uid, making sure that we keep | |
465 | * root privileges in the real uid. | |
466 | * | |
467 | * A portable solution would require a fork(), but we rely on Linux having the | |
468 | * BSD setreuid(). | |
469 | */ | |
470 | ||
29cc2a55 | 471 | int get_hushlogin_status(struct passwd *pwd, int force_check) |
78dd7450 OO |
472 | { |
473 | const char *files[] = { _PATH_HUSHLOGINS, _PATH_HUSHLOGIN, NULL }; | |
474 | const char *file; | |
475 | char buf[BUFSIZ]; | |
476 | int i; | |
477 | ||
478 | file = getlogindefs_str("HUSHLOGIN_FILE", NULL); | |
479 | if (file) { | |
480 | if (!*file) | |
481 | return 0; /* empty HUSHLOGIN_FILE defined */ | |
482 | ||
483 | files[0] = file; | |
484 | files[1] = NULL; | |
485 | } | |
486 | ||
487 | for (i = 0; files[i]; i++) { | |
488 | int ok = 0; | |
489 | ||
490 | file = files[i]; | |
491 | ||
492 | /* global hush-file */ | |
493 | if (*file == '/') { | |
494 | struct stat st; | |
495 | FILE *f; | |
496 | ||
497 | if (stat(file, &st) != 0) | |
498 | continue; /* file does not exist */ | |
499 | ||
500 | if (st.st_size == 0) | |
501 | return 1; /* for all accounts */ | |
502 | ||
503 | f = fopen(file, "r"); | |
504 | if (!f) | |
505 | continue; /* ignore errors... */ | |
506 | ||
507 | while (ok == 0 && fgets(buf, sizeof(buf), f)) { | |
b0f97de5 TS |
508 | if (buf[0] != '\0') |
509 | buf[strlen(buf) - 1] = '\0'; | |
78dd7450 OO |
510 | ok = !strcmp(buf, *buf == '/' ? pwd->pw_shell : |
511 | pwd->pw_name); | |
512 | } | |
513 | fclose(f); | |
514 | if (ok) | |
515 | return 1; /* found username/shell */ | |
516 | ||
517 | return 0; /* ignore per-account files */ | |
518 | } | |
519 | ||
520 | /* per-account setting */ | |
fdadefe5 | 521 | if (strlen(pwd->pw_dir) + strlen(file) + 2 > sizeof(buf)) |
78dd7450 | 522 | continue; |
29cc2a55 | 523 | |
977f98ee KZ |
524 | if (snprintf(buf, sizeof(buf), "%s/%s", pwd->pw_dir, file) < 0) |
525 | continue; | |
29cc2a55 OO |
526 | |
527 | if (force_check) { | |
78dd7450 OO |
528 | uid_t ruid = getuid(); |
529 | gid_t egid = getegid(); | |
530 | ||
78dd7450 OO |
531 | if (setregid(-1, pwd->pw_gid) == 0 && |
532 | setreuid(0, pwd->pw_uid) == 0) | |
533 | ok = effective_access(buf, O_RDONLY) == 0; | |
534 | ||
535 | if (setuid(0) != 0 || | |
536 | setreuid(ruid, 0) != 0 || | |
537 | setregid(-1, egid) != 0) { | |
538 | syslog(LOG_ALERT, _("hush login status: restore original IDs failed")); | |
539 | exit(EXIT_FAILURE); | |
540 | } | |
541 | if (ok) | |
542 | return 1; /* enabled by user */ | |
543 | } | |
29cc2a55 OO |
544 | else { |
545 | int rc; | |
546 | rc = effective_access(buf, O_RDONLY); | |
547 | if (rc == 0) | |
548 | return 1; | |
042f62df RP |
549 | |
550 | if (rc == -1 && errno == EACCES) | |
551 | return -1; | |
29cc2a55 OO |
552 | } |
553 | ||
78dd7450 OO |
554 | } |
555 | ||
556 | return 0; | |
557 | } | |
c82d9c97 KZ |
558 | #ifdef TEST_PROGRAM |
559 | int main(int argc, char *argv[]) | |
560 | { | |
561 | char *name, *type; | |
2c308875 | 562 | close_stdout_atexit(); |
c82d9c97 KZ |
563 | |
564 | if (argc <= 1) | |
565 | errx(EXIT_FAILURE, "usage: %s <filename> " | |
3f9c237d | 566 | "[<str|num|bool> <valname>]", argv[0]); |
c82d9c97 | 567 | |
9c44ac50 | 568 | logindefs_load_file(argv[1]); |
c82d9c97 | 569 | |
3f9c237d | 570 | if (argc != 4) { /* list all */ |
9e584ff3 | 571 | #ifdef HAVE_LIBECONF |
b6b7348e TK |
572 | int i; |
573 | char *keys[] = {"END", "EMPTY", "CRAZY3", "CRAZY2", "CRAZY1", | |
574 | "BOOLEAN", "NUMBER", "STRING", "HELLO_WORLD", | |
575 | NULL}; | |
9e584ff3 | 576 | |
b6b7348e | 577 | for (i = 0; keys[i] != NULL; i++) { |
9e584ff3 TK |
578 | char *value = NULL; |
579 | ||
580 | econf_getStringValue(file, NULL, keys[i], &value); | |
b6b7348e | 581 | printf ("%s: $%s: '%s'\n", argv[1], keys[i], value); |
9e584ff3 TK |
582 | } |
583 | ||
9e584ff3 TK |
584 | econf_free (file); |
585 | ||
586 | #else | |
c82d9c97 KZ |
587 | struct item *ptr; |
588 | ||
589 | for (ptr = list; ptr; ptr = ptr->next) | |
3f9c237d SK |
590 | printf("%s: $%s: '%s'\n", ptr->path, ptr->name, |
591 | ptr->value); | |
9e584ff3 | 592 | #endif |
c82d9c97 KZ |
593 | return EXIT_SUCCESS; |
594 | } | |
595 | ||
596 | type = argv[2]; | |
597 | name = argv[3]; | |
598 | ||
599 | if (strcmp(type, "str") == 0) | |
600 | printf("$%s: '%s'\n", name, getlogindefs_str(name, "DEFAULT")); | |
601 | else if (strcmp(type, "num") == 0) | |
602 | printf("$%s: '%ld'\n", name, getlogindefs_num(name, 0)); | |
603 | else if (strcmp(type, "bool") == 0) | |
3f9c237d SK |
604 | printf("$%s: '%s'\n", name, |
605 | getlogindefs_bool(name, 0) ? "Y" : "N"); | |
c82d9c97 KZ |
606 | |
607 | return EXIT_SUCCESS; | |
608 | } | |
609 | #endif |