]> git.ipfire.org Git - thirdparty/util-linux.git/blob - login-utils/logindefs.c
build-sys: release++ (v2.38.1)
[thirdparty/util-linux.git] / login-utils / logindefs.c
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 */
22 #include <assert.h>
23 #include <ctype.h>
24 #include <errno.h>
25 #include <limits.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <sys/syslog.h>
30 #include <sys/stat.h>
31 #include <sys/types.h>
32 #include <pwd.h>
33
34 #include "c.h"
35 #include "closestream.h"
36 #include "logindefs.h"
37 #include "nls.h"
38 #include "pathnames.h"
39 #include "xalloc.h"
40
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
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
95 void logindefs_load_file(const char *filename)
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')
109 continue; /* only comment or empty line */
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)
121 continue; /* empty line */
122
123 /* ignore space at begin of the line */
124 name = buf;
125 while (*name && isspace((unsigned)*name))
126 name++;
127
128 /* go to the end of the name */
129 data = name;
130 while (*data && !(isspace((unsigned)*data) || *data == '='))
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 */
139 while (*data
140 && (isspace((unsigned)*data) || *data == '='
141 || *data == '"'))
142 data++;
143
144 /* remove space at the end of the value */
145 p = data + strlen(data);
146 if (p > data)
147 p--;
148 while (p > data && (isspace((unsigned)*p) || *p == '"'))
149 *p-- = '\0';
150
151 store(name, data, filename);
152 }
153
154 fclose(f);
155 }
156
157 static void load_defaults(void)
158 {
159 if (logindefs_loader)
160 logindefs_loader(logindefs_loader_data);
161 else
162 logindefs_load_file(_PATH_LOGINDEFS);
163 }
164
165 static struct item *search(const char *name)
166 {
167 struct item *ptr;
168
169 if (!list)
170 load_defaults();
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 {
198 struct item *ptr = search(name);
199 return ptr && ptr->value ? (strcasecmp(ptr->value, "yes") == 0) : dflt;
200 }
201
202 unsigned long getlogindefs_num(const char *name, unsigned long dflt)
203 {
204 struct item *ptr = search(name);
205 char *end = NULL;
206 unsigned long retval;
207
208 if (!ptr || !ptr->value)
209 return dflt;
210
211 errno = 0;
212 retval = strtoul(ptr->value, &end, 0);
213 if (end && *end == '\0' && !errno)
214 return retval;
215
216 syslog(LOG_NOTICE, _("%s: %s contains invalid numerical value: %s"),
217 search_config(name), name, ptr->value);
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
238 #else /* !HAVE_LIBECONF */
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
250 static void load_defaults(void)
251 {
252 econf_err error;
253
254 if (file != NULL)
255 free_getlogindefs_data();
256
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)
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 {
276 econf_file *file_l = NULL, *file_m = NULL;
277 char *path;
278
279 logindefs_loader = NULL; /* No recursion */
280
281 #if USE_VENDORDIR
282 xasprintf(&path, _PATH_VENDORDIR"/%s", filename);
283
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
296 xasprintf(&path, "/etc/%s", filename);
297
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 }
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);
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 }
337 return value;
338 }
339
340 unsigned long getlogindefs_num(const char *name, unsigned long dflt)
341 {
342 uint64_t value;
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 }
383 if (value)
384 return value;
385
386 return xstrdup("");
387 }
388 #endif /* !HAVE_LIBECONF */
389
390 /*
391 * For compatibility with shadow-utils we have to support additional
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
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
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
471 int get_hushlogin_status(struct passwd *pwd, int force_check)
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)) {
508 if (buf[0] != '\0')
509 buf[strlen(buf) - 1] = '\0';
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 */
521 if (strlen(pwd->pw_dir) + strlen(file) + 2 > sizeof(buf))
522 continue;
523
524 if (snprintf(buf, sizeof(buf), "%s/%s", pwd->pw_dir, file) < 0)
525 continue;
526
527 if (force_check) {
528 uid_t ruid = getuid();
529 gid_t egid = getegid();
530
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 }
544 else {
545 int rc;
546 rc = effective_access(buf, O_RDONLY);
547 if (rc == 0)
548 return 1;
549
550 if (rc == -1 && errno == EACCES)
551 return -1;
552 }
553
554 }
555
556 return 0;
557 }
558 #ifdef TEST_PROGRAM
559 int main(int argc, char *argv[])
560 {
561 char *name, *type;
562 close_stdout_atexit();
563
564 if (argc <= 1)
565 errx(EXIT_FAILURE, "usage: %s <filename> "
566 "[<str|num|bool> <valname>]", argv[0]);
567
568 logindefs_load_file(argv[1]);
569
570 if (argc != 4) { /* list all */
571 #ifdef HAVE_LIBECONF
572 int i;
573 char *keys[] = {"END", "EMPTY", "CRAZY3", "CRAZY2", "CRAZY1",
574 "BOOLEAN", "NUMBER", "STRING", "HELLO_WORLD",
575 NULL};
576
577 for (i = 0; keys[i] != NULL; i++) {
578 char *value = NULL;
579
580 econf_getStringValue(file, NULL, keys[i], &value);
581 printf ("%s: $%s: '%s'\n", argv[1], keys[i], value);
582 }
583
584 econf_free (file);
585
586 #else
587 struct item *ptr;
588
589 for (ptr = list; ptr; ptr = ptr->next)
590 printf("%s: $%s: '%s'\n", ptr->path, ptr->name,
591 ptr->value);
592 #endif
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)
604 printf("$%s: '%s'\n", name,
605 getlogindefs_bool(name, 0) ? "Y" : "N");
606
607 return EXIT_SUCCESS;
608 }
609 #endif