]> git.ipfire.org Git - thirdparty/util-linux.git/blame - login-utils/chfn.c
libmount: (parser) fix memory leak on error before end-of-file
[thirdparty/util-linux.git] / login-utils / chfn.c
CommitLineData
6dbe3af9
KZ
1/*
2 * chfn.c -- change your finger information
3 * (c) 1994 by salvatore valente <svalente@athena.mit.edu>
8c24b6aa 4 * (c) 2012 by Cody Maloney <cmaloney@theoreticalchaos.com>
6dbe3af9
KZ
5 *
6 * this program is free software. you can redistribute it and
7 * modify it under the terms of the gnu general public license.
8 * there is no warranty.
9 *
fd6b7a7f 10 * $Author: aebr $
2b6fc908
KZ
11 * $Revision: 1.18 $
12 * $Date: 1998/06/11 22:30:11 $
726f69e2
KZ
13 *
14 * Updated Thu Oct 12 09:19:26 1995 by faith@cs.unc.edu with security
15 * patches from Zefram <A.Main@dcs.warwick.ac.uk>
6dbe3af9 16 *
7eda085c
KZ
17 * Hacked by Peter Breitenlohner, peb@mppmu.mpg.de,
18 * to remove trailing empty fields. Oct 5, 96.
19 *
b50945d4 20 * 1999-02-22 Arkadiusz Miƛkiewicz <misiek@pld.ORG.PL>
7eda085c 21 * - added Native Language Support
6dbe3af9
KZ
22 */
23
6dbe3af9 24#include <ctype.h>
3ca10299 25#include <errno.h>
6dbe3af9 26#include <getopt.h>
3ca10299 27#include <pwd.h>
8187b555 28#include <stdbool.h>
3ca10299
SK
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#include <sys/types.h>
33#include <unistd.h>
39941b4e 34
3ca10299
SK
35#include "c.h"
36#include "env.h"
439cdf1e 37#include "closestream.h"
66ee8158 38#include "islocal.h"
3ca10299 39#include "nls.h"
66ee8158 40#include "setpwnam.h"
8abcf290 41#include "strutils.h"
39941b4e 42#include "xalloc.h"
e88f0059 43#include "logindefs.h"
fd6b7a7f 44
144ae70e
SK
45#include "ch-common.h"
46
48d7b13a 47#ifdef HAVE_LIBSELINUX
3ca10299 48# include <selinux/selinux.h>
3ca10299 49# include "selinux_utils.h"
d03dd608
KZ
50#endif
51
8c24b6aa
CM
52#ifdef HAVE_LIBUSER
53# include <libuser/user.h>
54# include "libuser.h"
d86918b6 55#elif CHFN_CHSH_PASSWORD
8c24b6aa
CM
56# include "auth.h"
57#endif
58
e41ae450
SK
59#ifdef HAVE_LIBREADLINE
60# define _FUNCTION_DEF
61# include <readline/readline.h>
62#endif
63
6dbe3af9 64struct finfo {
3ca10299
SK
65 char *full_name;
66 char *office;
67 char *office_phone;
68 char *home_phone;
69 char *other;
6dbe3af9
KZ
70};
71
d5fdba03 72struct chfn_control {
5fe1c32f
SK
73 struct passwd *pw;
74 char *username;
75 /* "oldf" Contains the users original finger information.
76 * "newf" Contains the changed finger information, and contains
77 * NULL in fields that haven't been changed.
78 * In the end, "newf" is folded into "oldf". */
79 struct finfo oldf, newf;
d5fdba03 80 unsigned int
e88f0059
SK
81 allow_fullname:1, /* The login.defs restriction */
82 allow_room:1, /* see: man login.defs(5) */
83 allow_work:1, /* and look for CHFN_RESTRICT */
84 allow_home:1, /* keyword for these four. */
f723cbf5 85 changed:1, /* is change requested */
d5fdba03
SK
86 interactive:1; /* whether to prompt for fields or not */
87};
88
7eda085c 89/* we do not accept gecos field sizes longer than MAX_FIELD_SIZE */
5c36a0eb
KZ
90#define MAX_FIELD_SIZE 256
91
6e1eda6f 92static void __attribute__((__noreturn__)) usage(void)
39941b4e 93{
6e1eda6f 94 FILE *fp = stdout;
3ca10299 95 fputs(USAGE_HEADER, fp);
09af3db4 96 fprintf(fp, _(" %s [options] [<username>]\n"), program_invocation_short_name);
451dbcfa
BS
97
98 fputs(USAGE_SEPARATOR, fp);
99 fputs(_("Change your finger information.\n"), fp);
100
3ca10299
SK
101 fputs(USAGE_OPTIONS, fp);
102 fputs(_(" -f, --full-name <full-name> real name\n"), fp);
103 fputs(_(" -o, --office <office> office number\n"), fp);
104 fputs(_(" -p, --office-phone <phone> office phone number\n"), fp);
105 fputs(_(" -h, --home-phone <phone> home phone number\n"), fp);
106 fputs(USAGE_SEPARATOR, fp);
b3054454
RM
107 printf( " -u, --help %s\n", USAGE_OPTSTR_HELP);
108 printf( " -v, --version %s\n", USAGE_OPTSTR_VERSION);
f45f3ec3 109 printf(USAGE_MAN_TAIL("chfn(1)"));
6e1eda6f 110 exit(EXIT_SUCCESS);
39941b4e
MP
111}
112
24016335
SK
113/*
114 * check_gecos_string () --
115 * check that the given gecos string is legal. if it's not legal,
116 * output "msg" followed by a description of the problem, and return (-1).
117 */
5a57c00a 118static int check_gecos_string(const char *msg, char *gecos)
3ca10299 119{
5a57c00a 120 const size_t len = strlen(gecos);
3ca10299 121
5a57c00a
SK
122 if (MAX_FIELD_SIZE < len) {
123 warnx(_("field %s is too long"), msg);
24016335 124 return -1;
3ca10299 125 }
144ae70e
SK
126 if (illegal_passwd_chars(gecos)) {
127 warnx(_("%s: has illegal characters"), gecos);
128 return -1;
3ca10299 129 }
24016335 130 return 0;
6dbe3af9
KZ
131}
132
133/*
134 * parse_argv () --
135 * parse the command line arguments.
136 * returns true if no information beyond the username was given.
137 */
5fe1c32f 138static void parse_argv(struct chfn_control *ctl, int argc, char **argv)
6dbe3af9 139{
e4efecc4
SK
140 int index, c, status = 0;
141 static const struct option long_options[] = {
87918040
SK
142 { "full-name", required_argument, NULL, 'f' },
143 { "office", required_argument, NULL, 'o' },
144 { "office-phone", required_argument, NULL, 'p' },
145 { "home-phone", required_argument, NULL, 'h' },
146 { "help", no_argument, NULL, 'u' },
147 { "version", no_argument, NULL, 'v' },
148 { NULL, 0, NULL, 0 },
3ca10299
SK
149 };
150
e4efecc4
SK
151 while ((c = getopt_long(argc, argv, "f:r:p:h:o:uv", long_options,
152 &index)) != -1) {
3ca10299
SK
153 switch (c) {
154 case 'f':
e88f0059
SK
155 if (!ctl->allow_fullname)
156 errx(EXIT_FAILURE, _("login.defs forbids setting %s"), _("Name"));
5fe1c32f 157 ctl->newf.full_name = optarg;
e4efecc4 158 status += check_gecos_string(_("Name"), optarg);
3ca10299
SK
159 break;
160 case 'o':
e88f0059
SK
161 if (!ctl->allow_room)
162 errx(EXIT_FAILURE, _("login.defs forbids setting %s"), _("Office"));
5fe1c32f 163 ctl->newf.office = optarg;
e4efecc4 164 status += check_gecos_string(_("Office"), optarg);
3ca10299
SK
165 break;
166 case 'p':
e88f0059
SK
167 if (!ctl->allow_work)
168 errx(EXIT_FAILURE, _("login.defs forbids setting %s"), _("Office Phone"));
5fe1c32f 169 ctl->newf.office_phone = optarg;
e4efecc4 170 status += check_gecos_string(_("Office Phone"), optarg);
3ca10299
SK
171 break;
172 case 'h':
e88f0059
SK
173 if (!ctl->allow_home)
174 errx(EXIT_FAILURE, _("login.defs forbids setting %s"), _("Home Phone"));
5fe1c32f 175 ctl->newf.home_phone = optarg;
e4efecc4 176 status += check_gecos_string(_("Home Phone"), optarg);
3ca10299 177 break;
e4efecc4 178 case 'v':
2c308875 179 print_version(EXIT_SUCCESS);
e4efecc4 180 case 'u':
6e1eda6f 181 usage();
3ca10299 182 default:
677ec86c 183 errtryhelp(EXIT_FAILURE);
3ca10299 184 }
f723cbf5 185 ctl->changed = 1;
e4efecc4 186 ctl->interactive = 0;
6dbe3af9 187 }
e4efecc4
SK
188 if (status != 0)
189 exit(EXIT_FAILURE);
3ca10299
SK
190 /* done parsing arguments. check for a username. */
191 if (optind < argc) {
6e1eda6f
RM
192 if (optind + 1 < argc) {
193 warnx(_("cannot handle multiple usernames"));
194 errtryhelp(EXIT_FAILURE);
195 }
5fe1c32f 196 ctl->username = argv[optind];
6dbe3af9 197 }
6dbe3af9
KZ
198}
199
6dbe3af9
KZ
200/*
201 * parse_passwd () --
3ca10299 202 * take a struct password and fill in the fields of the struct finfo.
6dbe3af9 203 */
5fe1c32f 204static void parse_passwd(struct chfn_control *ctl)
6dbe3af9 205{
3ca10299 206 char *gecos;
58985f67 207
5fe1c32f 208 if (!ctl->pw)
58985f67 209 return;
58985f67 210 /* use pw_gecos - we take a copy since PAM destroys the original */
5fe1c32f 211 gecos = xstrdup(ctl->pw->pw_gecos);
58985f67 212 /* extract known fields */
5fe1c32f
SK
213 ctl->oldf.full_name = strsep(&gecos, ",");
214 ctl->oldf.office = strsep(&gecos, ",");
215 ctl->oldf.office_phone = strsep(&gecos, ",");
216 ctl->oldf.home_phone = strsep(&gecos, ",");
58985f67
SK
217 /* extra fields contain site-specific information, and can
218 * not be changed by this version of chfn. */
5fe1c32f 219 ctl->oldf.other = strsep(&gecos, ",");
6dbe3af9
KZ
220}
221
6dbe3af9 222/*
d9e1ac99 223 * ask_new_field () --
6dbe3af9
KZ
224 * ask the user for a given field and check that the string is legal.
225 */
d9e1ac99
SK
226static char *ask_new_field(struct chfn_control *ctl, const char *question,
227 char *def_val)
6dbe3af9 228{
3ca10299 229 int len;
e41ae450
SK
230 char *buf;
231#ifndef HAVE_LIBREADLINE
232 size_t dummy = 0;
233#endif
3ca10299 234
5a57c00a
SK
235 if (!def_val)
236 def_val = "";
3ca10299 237 while (true) {
3ca10299 238 printf("%s [%s]: ", question, def_val);
5a57c00a 239 __fpurge(stdin);
e41ae450 240#ifdef HAVE_LIBREADLINE
36b60841 241 rl_bind_key('\t', rl_insert);
e41ae450
SK
242 if ((buf = readline(NULL)) == NULL)
243#else
244 if (getline(&buf, &dummy, stdin) < 0)
245#endif
3ca10299 246 errx(EXIT_FAILURE, _("Aborted."));
5a57c00a 247 /* remove white spaces from string end */
e41ae450
SK
248 ltrim_whitespace((unsigned char *) buf);
249 len = rtrim_whitespace((unsigned char *) buf);
250 if (len == 0) {
251 free(buf);
f723cbf5 252 return xstrdup(def_val);
e41ae450
SK
253 }
254 if (!strcasecmp(buf, "none")) {
255 free(buf);
f723cbf5
SK
256 ctl->changed = 1;
257 return xstrdup("");
258 }
e41ae450 259 if (check_gecos_string(question, buf) >= 0)
3ca10299
SK
260 break;
261 }
f723cbf5 262 ctl->changed = 1;
e41ae450 263 return buf;
6dbe3af9
KZ
264}
265
e88f0059
SK
266/*
267 * get_login_defs()
268 * find /etc/login.defs CHFN_RESTRICT and save restrictions to run time
269 */
270static void get_login_defs(struct chfn_control *ctl)
271{
272 const char *s;
273 size_t i;
274 int broken = 0;
275
276 /* real root does not have restrictions */
277 if (geteuid() == getuid() && getuid() == 0) {
278 ctl->allow_fullname = ctl->allow_room = ctl->allow_work = ctl->allow_home = 1;
279 return;
280 }
281 s = getlogindefs_str("CHFN_RESTRICT", "");
282 if (!strcmp(s, "yes")) {
283 ctl->allow_room = ctl->allow_work = ctl->allow_home = 1;
284 return;
285 }
286 if (!strcmp(s, "no")) {
287 ctl->allow_fullname = ctl->allow_room = ctl->allow_work = ctl->allow_home = 1;
288 return;
289 }
290 for (i = 0; s[i]; i++) {
291 switch (s[i]) {
292 case 'f':
293 ctl->allow_fullname = 1;
294 break;
295 case 'r':
296 ctl->allow_room = 1;
297 break;
298 case 'w':
299 ctl->allow_work = 1;
300 break;
301 case 'h':
302 ctl->allow_home = 1;
303 break;
304 default:
305 broken = 1;
306 }
307 }
308 if (broken)
309 warnx(_("%s: CHFN_RESTRICT has unexpected value: %s"), _PATH_LOGINDEFS, s);
310 if (!ctl->allow_fullname && !ctl->allow_room && !ctl->allow_work && !ctl->allow_home)
311 errx(EXIT_FAILURE, _("%s: CHFN_RESTRICT does not allow any changes"), _PATH_LOGINDEFS);
e88f0059
SK
312}
313
6dbe3af9 314/*
24016335
SK
315 * ask_info () --
316 * prompt the user for the finger information and store it.
6dbe3af9 317 */
5fe1c32f 318static void ask_info(struct chfn_control *ctl)
6dbe3af9 319{
e88f0059
SK
320 if (ctl->allow_fullname)
321 ctl->newf.full_name = ask_new_field(ctl, _("Name"), ctl->oldf.full_name);
322 if (ctl->allow_room)
323 ctl->newf.office = ask_new_field(ctl, _("Office"), ctl->oldf.office);
324 if (ctl->allow_work)
325 ctl->newf.office_phone = ask_new_field(ctl, _("Office Phone"), ctl->oldf.office_phone);
326 if (ctl->allow_home)
327 ctl->newf.home_phone = ask_new_field(ctl, _("Home Phone"), ctl->oldf.home_phone);
328 putchar('\n');
6dbe3af9
KZ
329}
330
331/*
f723cbf5
SK
332 * find_field () --
333 * find field value in uninteractive mode; can be new, old, or blank
6dbe3af9 334 */
f723cbf5 335static char *find_field(char *nf, char *of)
6dbe3af9 336{
f723cbf5
SK
337 if (nf)
338 return nf;
339 if (of)
340 return of;
341 return xstrdup("");
342}
3ca10299 343
f723cbf5
SK
344/*
345 * add_missing () --
346 * add not supplied field values when in uninteractive mode
347 */
348static void add_missing(struct chfn_control *ctl)
349{
350 ctl->newf.full_name = find_field(ctl->newf.full_name, ctl->oldf.full_name);
351 ctl->newf.office = find_field(ctl->newf.office, ctl->oldf.office);
352 ctl->newf.office_phone = find_field(ctl->newf.office_phone, ctl->oldf.office_phone);
353 ctl->newf.home_phone = find_field(ctl->newf.home_phone, ctl->oldf.home_phone);
354 ctl->newf.other = find_field(ctl->newf.other, ctl->oldf.other);
355 printf("\n");
6dbe3af9
KZ
356}
357
358/*
359 * save_new_data () --
360 * save the given finger info in /etc/passwd.
361 * return zero on success.
362 */
5fe1c32f 363static int save_new_data(struct chfn_control *ctl)
6dbe3af9 364{
3ca10299
SK
365 char *gecos;
366 int len;
367
3ca10299 368 /* create the new gecos string */
f723cbf5
SK
369 len = xasprintf(&gecos, "%s,%s,%s,%s,%s",
370 ctl->newf.full_name,
371 ctl->newf.office,
372 ctl->newf.office_phone,
373 ctl->newf.home_phone,
374 ctl->newf.other);
3ca10299 375
5fe1c32f 376 /* remove trailing empty fields (but not subfields of ctl->newf.other) */
9210c0d2 377 if (!ctl->newf.other || !*ctl->newf.other) {
3ca10299
SK
378 while (len > 0 && gecos[len - 1] == ',')
379 len--;
380 gecos[len] = 0;
381 }
382
8c24b6aa 383#ifdef HAVE_LIBUSER
5fe1c32f 384 if (set_value_libuser("chfn", ctl->username, ctl->pw->pw_uid,
d86918b6 385 LU_GECOS, gecos) < 0) {
8c24b6aa 386#else /* HAVE_LIBUSER */
3ca10299 387 /* write the new struct passwd to the passwd file. */
5fe1c32f 388 ctl->pw->pw_gecos = gecos;
bde91c85 389 if (setpwnam(ctl->pw, ".chfn") < 0) {
d86918b6 390 warn("setpwnam failed");
8c24b6aa 391#endif
3ca10299
SK
392 printf(_
393 ("Finger information *NOT* changed. Try again later.\n"));
394 return -1;
395 }
1b7a19eb 396 free(gecos);
3ca10299
SK
397 printf(_("Finger information changed.\n"));
398 return 0;
6dbe3af9 399}
24016335
SK
400
401int main(int argc, char **argv)
402{
403 uid_t uid;
d5fdba03
SK
404 struct chfn_control ctl = {
405 .interactive = 1
406 };
24016335
SK
407
408 sanitize_env();
409 setlocale(LC_ALL, ""); /* both for messages and for iscntrl() below */
410 bindtextdomain(PACKAGE, LOCALEDIR);
411 textdomain(PACKAGE);
2c308875
KZ
412 close_stdout_atexit();
413
24016335 414 uid = getuid();
24016335 415
e88f0059
SK
416 /* check /etc/login.defs CHFN_RESTRICT */
417 get_login_defs(&ctl);
418
5fe1c32f
SK
419 parse_argv(&ctl, argc, argv);
420 if (!ctl.username) {
421 ctl.pw = getpwuid(uid);
422 if (!ctl.pw)
24016335
SK
423 errx(EXIT_FAILURE, _("you (user %d) don't exist."),
424 uid);
5fe1c32f 425 ctl.username = ctl.pw->pw_name;
24016335 426 } else {
5fe1c32f
SK
427 ctl.pw = getpwnam(ctl.username);
428 if (!ctl.pw)
24016335 429 errx(EXIT_FAILURE, _("user \"%s\" does not exist."),
5fe1c32f 430 ctl.username);
24016335 431 }
5fe1c32f 432 parse_passwd(&ctl);
24016335 433#ifndef HAVE_LIBUSER
5fe1c32f 434 if (!(is_local(ctl.username)))
24016335
SK
435 errx(EXIT_FAILURE, _("can only change local entries"));
436#endif
437
438#ifdef HAVE_LIBSELINUX
439 if (is_selinux_enabled() > 0) {
440 if (uid == 0) {
dd5ef107
KZ
441 access_vector_t av = get_access_vector("passwd", "chfn");
442
443 if (selinux_check_passwd_access(av) != 0) {
24016335
SK
444 security_context_t user_context;
445 if (getprevcon(&user_context) < 0)
446 user_context = NULL;
447 errx(EXIT_FAILURE,
448 _("%s is not authorized to change "
449 "the finger info of %s"),
450 user_context ? : _("Unknown user context"),
5fe1c32f 451 ctl.username);
24016335
SK
452 }
453 }
454 if (setupDefaultContext(_PATH_PASSWD))
455 errx(EXIT_FAILURE,
456 _("can't set default context for %s"), _PATH_PASSWD);
457 }
458#endif
459
460#ifdef HAVE_LIBUSER
461 /* If we're setuid and not really root, disallow the password change. */
5fe1c32f 462 if (geteuid() != getuid() && uid != ctl.pw->pw_uid) {
24016335 463#else
bf6c15ed 464 if (uid != 0 && uid != ctl.pw->pw_uid) {
24016335
SK
465#endif
466 errno = EACCES;
467 err(EXIT_FAILURE, _("running UID doesn't match UID of user we're "
468 "altering, change denied"));
469 }
470
5fe1c32f 471 printf(_("Changing finger information for %s.\n"), ctl.username);
24016335
SK
472
473#if !defined(HAVE_LIBUSER) && defined(CHFN_CHSH_PASSWORD)
5fe1c32f 474 if (!auth_pam("chfn", uid, ctl.username)) {
24016335
SK
475 return EXIT_FAILURE;
476 }
477#endif
478
d5fdba03 479 if (ctl.interactive)
5fe1c32f 480 ask_info(&ctl);
e88f0059
SK
481
482 add_missing(&ctl);
24016335 483
f723cbf5 484 if (!ctl.changed) {
24016335
SK
485 printf(_("Finger information not changed.\n"));
486 return EXIT_SUCCESS;
487 }
488
5fe1c32f 489 return save_new_data(&ctl) == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
24016335 490}