]> git.ipfire.org Git - thirdparty/util-linux.git/blame - login-utils/chfn.c
tests: add blkid script to test whole-disk MBR devices
[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
SK
48# include <selinux/selinux.h>
49# include <selinux/av_permissions.h>
50# include "selinux_utils.h"
d03dd608
KZ
51#endif
52
8c24b6aa
CM
53#ifdef HAVE_LIBUSER
54# include <libuser/user.h>
55# include "libuser.h"
d86918b6 56#elif CHFN_CHSH_PASSWORD
8c24b6aa
CM
57# include "auth.h"
58#endif
59
6dbe3af9 60struct finfo {
3ca10299
SK
61 char *full_name;
62 char *office;
63 char *office_phone;
64 char *home_phone;
65 char *other;
6dbe3af9
KZ
66};
67
d5fdba03 68struct chfn_control {
5fe1c32f
SK
69 struct passwd *pw;
70 char *username;
71 /* "oldf" Contains the users original finger information.
72 * "newf" Contains the changed finger information, and contains
73 * NULL in fields that haven't been changed.
74 * In the end, "newf" is folded into "oldf". */
75 struct finfo oldf, newf;
d5fdba03 76 unsigned int
e88f0059
SK
77 allow_fullname:1, /* The login.defs restriction */
78 allow_room:1, /* see: man login.defs(5) */
79 allow_work:1, /* and look for CHFN_RESTRICT */
80 allow_home:1, /* keyword for these four. */
f723cbf5 81 changed:1, /* is change requested */
d5fdba03
SK
82 interactive:1; /* whether to prompt for fields or not */
83};
84
7eda085c 85/* we do not accept gecos field sizes longer than MAX_FIELD_SIZE */
5c36a0eb
KZ
86#define MAX_FIELD_SIZE 256
87
39941b4e
MP
88static void __attribute__((__noreturn__)) usage(FILE *fp)
89{
3ca10299 90 fputs(USAGE_HEADER, fp);
09af3db4 91 fprintf(fp, _(" %s [options] [<username>]\n"), program_invocation_short_name);
451dbcfa
BS
92
93 fputs(USAGE_SEPARATOR, fp);
94 fputs(_("Change your finger information.\n"), fp);
95
3ca10299
SK
96 fputs(USAGE_OPTIONS, fp);
97 fputs(_(" -f, --full-name <full-name> real name\n"), fp);
98 fputs(_(" -o, --office <office> office number\n"), fp);
99 fputs(_(" -p, --office-phone <phone> office phone number\n"), fp);
100 fputs(_(" -h, --home-phone <phone> home phone number\n"), fp);
101 fputs(USAGE_SEPARATOR, fp);
496083ba
SK
102 fputs(_(" -u, --help display this help and exit\n"), fp);
103 fputs(_(" -v, --version output version information and exit\n"), fp);
3ca10299
SK
104 fprintf(fp, USAGE_MAN_TAIL("chfn(1)"));
105 exit(fp == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
39941b4e
MP
106}
107
24016335
SK
108/*
109 * check_gecos_string () --
110 * check that the given gecos string is legal. if it's not legal,
111 * output "msg" followed by a description of the problem, and return (-1).
112 */
5a57c00a 113static int check_gecos_string(const char *msg, char *gecos)
3ca10299 114{
5a57c00a 115 const size_t len = strlen(gecos);
3ca10299 116
5a57c00a
SK
117 if (MAX_FIELD_SIZE < len) {
118 warnx(_("field %s is too long"), msg);
24016335 119 return -1;
3ca10299 120 }
144ae70e
SK
121 if (illegal_passwd_chars(gecos)) {
122 warnx(_("%s: has illegal characters"), gecos);
123 return -1;
3ca10299 124 }
24016335 125 return 0;
6dbe3af9
KZ
126}
127
128/*
129 * parse_argv () --
130 * parse the command line arguments.
131 * returns true if no information beyond the username was given.
132 */
5fe1c32f 133static void parse_argv(struct chfn_control *ctl, int argc, char **argv)
6dbe3af9 134{
e4efecc4
SK
135 int index, c, status = 0;
136 static const struct option long_options[] = {
3ca10299
SK
137 {"full-name", required_argument, 0, 'f'},
138 {"office", required_argument, 0, 'o'},
139 {"office-phone", required_argument, 0, 'p'},
140 {"home-phone", required_argument, 0, 'h'},
141 {"help", no_argument, 0, 'u'},
142 {"version", no_argument, 0, 'v'},
143 {NULL, no_argument, 0, '0'},
144 };
145
e4efecc4
SK
146 while ((c = getopt_long(argc, argv, "f:r:p:h:o:uv", long_options,
147 &index)) != -1) {
3ca10299
SK
148 switch (c) {
149 case 'f':
e88f0059
SK
150 if (!ctl->allow_fullname)
151 errx(EXIT_FAILURE, _("login.defs forbids setting %s"), _("Name"));
5fe1c32f 152 ctl->newf.full_name = optarg;
e4efecc4 153 status += check_gecos_string(_("Name"), optarg);
3ca10299
SK
154 break;
155 case 'o':
e88f0059
SK
156 if (!ctl->allow_room)
157 errx(EXIT_FAILURE, _("login.defs forbids setting %s"), _("Office"));
5fe1c32f 158 ctl->newf.office = optarg;
e4efecc4 159 status += check_gecos_string(_("Office"), optarg);
3ca10299
SK
160 break;
161 case 'p':
e88f0059
SK
162 if (!ctl->allow_work)
163 errx(EXIT_FAILURE, _("login.defs forbids setting %s"), _("Office Phone"));
5fe1c32f 164 ctl->newf.office_phone = optarg;
e4efecc4 165 status += check_gecos_string(_("Office Phone"), optarg);
3ca10299
SK
166 break;
167 case 'h':
e88f0059
SK
168 if (!ctl->allow_home)
169 errx(EXIT_FAILURE, _("login.defs forbids setting %s"), _("Home Phone"));
5fe1c32f 170 ctl->newf.home_phone = optarg;
e4efecc4 171 status += check_gecos_string(_("Home Phone"), optarg);
3ca10299 172 break;
e4efecc4
SK
173 case 'v':
174 printf(UTIL_LINUX_VERSION);
175 exit(EXIT_SUCCESS);
176 case 'u':
177 usage(stdout);
3ca10299
SK
178 default:
179 usage(stderr);
180 }
f723cbf5 181 ctl->changed = 1;
e4efecc4 182 ctl->interactive = 0;
6dbe3af9 183 }
e4efecc4
SK
184 if (status != 0)
185 exit(EXIT_FAILURE);
3ca10299
SK
186 /* done parsing arguments. check for a username. */
187 if (optind < argc) {
188 if (optind + 1 < argc)
189 usage(stderr);
5fe1c32f 190 ctl->username = argv[optind];
6dbe3af9 191 }
d5fdba03 192 return;
6dbe3af9
KZ
193}
194
6dbe3af9
KZ
195/*
196 * parse_passwd () --
3ca10299 197 * take a struct password and fill in the fields of the struct finfo.
6dbe3af9 198 */
5fe1c32f 199static void parse_passwd(struct chfn_control *ctl)
6dbe3af9 200{
3ca10299 201 char *gecos;
58985f67 202
5fe1c32f 203 if (!ctl->pw)
58985f67 204 return;
58985f67 205 /* use pw_gecos - we take a copy since PAM destroys the original */
5fe1c32f 206 gecos = xstrdup(ctl->pw->pw_gecos);
58985f67 207 /* extract known fields */
5fe1c32f
SK
208 ctl->oldf.full_name = strsep(&gecos, ",");
209 ctl->oldf.office = strsep(&gecos, ",");
210 ctl->oldf.office_phone = strsep(&gecos, ",");
211 ctl->oldf.home_phone = strsep(&gecos, ",");
58985f67
SK
212 /* extra fields contain site-specific information, and can
213 * not be changed by this version of chfn. */
5fe1c32f 214 ctl->oldf.other = strsep(&gecos, ",");
6dbe3af9
KZ
215}
216
6dbe3af9 217/*
d9e1ac99 218 * ask_new_field () --
6dbe3af9
KZ
219 * ask the user for a given field and check that the string is legal.
220 */
d9e1ac99
SK
221static char *ask_new_field(struct chfn_control *ctl, const char *question,
222 char *def_val)
6dbe3af9 223{
3ca10299 224 int len;
5a57c00a
SK
225 char *ans;
226 char buf[MAX_FIELD_SIZE + 2];
3ca10299 227
5a57c00a
SK
228 if (!def_val)
229 def_val = "";
3ca10299 230 while (true) {
3ca10299 231 printf("%s [%s]: ", question, def_val);
5a57c00a 232 __fpurge(stdin);
3ca10299
SK
233 if (fgets(buf, sizeof(buf), stdin) == NULL)
234 errx(EXIT_FAILURE, _("Aborted."));
3ca10299 235 ans = buf;
5a57c00a
SK
236 /* remove white spaces from string end */
237 ltrim_whitespace((unsigned char *) ans);
238 len = rtrim_whitespace((unsigned char *) ans);
239 if (len == 0)
f723cbf5
SK
240 return xstrdup(def_val);
241 if (!strcasecmp(ans, "none")) {
242 ctl->changed = 1;
243 return xstrdup("");
244 }
5a57c00a 245 if (check_gecos_string(question, ans) >= 0)
3ca10299
SK
246 break;
247 }
f723cbf5 248 ctl->changed = 1;
5a57c00a 249 return xstrdup(ans);
6dbe3af9
KZ
250}
251
e88f0059
SK
252/*
253 * get_login_defs()
254 * find /etc/login.defs CHFN_RESTRICT and save restrictions to run time
255 */
256static void get_login_defs(struct chfn_control *ctl)
257{
258 const char *s;
259 size_t i;
260 int broken = 0;
261
262 /* real root does not have restrictions */
263 if (geteuid() == getuid() && getuid() == 0) {
264 ctl->allow_fullname = ctl->allow_room = ctl->allow_work = ctl->allow_home = 1;
265 return;
266 }
267 s = getlogindefs_str("CHFN_RESTRICT", "");
268 if (!strcmp(s, "yes")) {
269 ctl->allow_room = ctl->allow_work = ctl->allow_home = 1;
270 return;
271 }
272 if (!strcmp(s, "no")) {
273 ctl->allow_fullname = ctl->allow_room = ctl->allow_work = ctl->allow_home = 1;
274 return;
275 }
276 for (i = 0; s[i]; i++) {
277 switch (s[i]) {
278 case 'f':
279 ctl->allow_fullname = 1;
280 break;
281 case 'r':
282 ctl->allow_room = 1;
283 break;
284 case 'w':
285 ctl->allow_work = 1;
286 break;
287 case 'h':
288 ctl->allow_home = 1;
289 break;
290 default:
291 broken = 1;
292 }
293 }
294 if (broken)
295 warnx(_("%s: CHFN_RESTRICT has unexpected value: %s"), _PATH_LOGINDEFS, s);
296 if (!ctl->allow_fullname && !ctl->allow_room && !ctl->allow_work && !ctl->allow_home)
297 errx(EXIT_FAILURE, _("%s: CHFN_RESTRICT does not allow any changes"), _PATH_LOGINDEFS);
298 return;
299}
300
6dbe3af9 301/*
24016335
SK
302 * ask_info () --
303 * prompt the user for the finger information and store it.
6dbe3af9 304 */
5fe1c32f 305static void ask_info(struct chfn_control *ctl)
6dbe3af9 306{
e88f0059
SK
307 if (ctl->allow_fullname)
308 ctl->newf.full_name = ask_new_field(ctl, _("Name"), ctl->oldf.full_name);
309 if (ctl->allow_room)
310 ctl->newf.office = ask_new_field(ctl, _("Office"), ctl->oldf.office);
311 if (ctl->allow_work)
312 ctl->newf.office_phone = ask_new_field(ctl, _("Office Phone"), ctl->oldf.office_phone);
313 if (ctl->allow_home)
314 ctl->newf.home_phone = ask_new_field(ctl, _("Home Phone"), ctl->oldf.home_phone);
315 putchar('\n');
6dbe3af9
KZ
316}
317
318/*
f723cbf5
SK
319 * find_field () --
320 * find field value in uninteractive mode; can be new, old, or blank
6dbe3af9 321 */
f723cbf5 322static char *find_field(char *nf, char *of)
6dbe3af9 323{
f723cbf5
SK
324 if (nf)
325 return nf;
326 if (of)
327 return of;
328 return xstrdup("");
329}
3ca10299 330
f723cbf5
SK
331/*
332 * add_missing () --
333 * add not supplied field values when in uninteractive mode
334 */
335static void add_missing(struct chfn_control *ctl)
336{
337 ctl->newf.full_name = find_field(ctl->newf.full_name, ctl->oldf.full_name);
338 ctl->newf.office = find_field(ctl->newf.office, ctl->oldf.office);
339 ctl->newf.office_phone = find_field(ctl->newf.office_phone, ctl->oldf.office_phone);
340 ctl->newf.home_phone = find_field(ctl->newf.home_phone, ctl->oldf.home_phone);
341 ctl->newf.other = find_field(ctl->newf.other, ctl->oldf.other);
342 printf("\n");
6dbe3af9
KZ
343}
344
345/*
346 * save_new_data () --
347 * save the given finger info in /etc/passwd.
348 * return zero on success.
349 */
5fe1c32f 350static int save_new_data(struct chfn_control *ctl)
6dbe3af9 351{
3ca10299
SK
352 char *gecos;
353 int len;
354
3ca10299 355 /* create the new gecos string */
f723cbf5
SK
356 len = xasprintf(&gecos, "%s,%s,%s,%s,%s",
357 ctl->newf.full_name,
358 ctl->newf.office,
359 ctl->newf.office_phone,
360 ctl->newf.home_phone,
361 ctl->newf.other);
3ca10299 362
5fe1c32f 363 /* remove trailing empty fields (but not subfields of ctl->newf.other) */
f723cbf5 364 if (!ctl->newf.other) {
3ca10299
SK
365 while (len > 0 && gecos[len - 1] == ',')
366 len--;
367 gecos[len] = 0;
368 }
369
8c24b6aa 370#ifdef HAVE_LIBUSER
5fe1c32f 371 if (set_value_libuser("chfn", ctl->username, ctl->pw->pw_uid,
d86918b6 372 LU_GECOS, gecos) < 0) {
8c24b6aa 373#else /* HAVE_LIBUSER */
3ca10299 374 /* write the new struct passwd to the passwd file. */
5fe1c32f
SK
375 ctl->pw->pw_gecos = gecos;
376 if (setpwnam(ctl->pw) < 0) {
d86918b6 377 warn("setpwnam failed");
8c24b6aa 378#endif
3ca10299
SK
379 printf(_
380 ("Finger information *NOT* changed. Try again later.\n"));
381 return -1;
382 }
1b7a19eb 383 free(gecos);
3ca10299
SK
384 printf(_("Finger information changed.\n"));
385 return 0;
6dbe3af9 386}
24016335
SK
387
388int main(int argc, char **argv)
389{
390 uid_t uid;
d5fdba03
SK
391 struct chfn_control ctl = {
392 .interactive = 1
393 };
24016335
SK
394
395 sanitize_env();
396 setlocale(LC_ALL, ""); /* both for messages and for iscntrl() below */
397 bindtextdomain(PACKAGE, LOCALEDIR);
398 textdomain(PACKAGE);
399 atexit(close_stdout);
24016335 400 uid = getuid();
24016335 401
e88f0059
SK
402 /* check /etc/login.defs CHFN_RESTRICT */
403 get_login_defs(&ctl);
404
5fe1c32f
SK
405 parse_argv(&ctl, argc, argv);
406 if (!ctl.username) {
407 ctl.pw = getpwuid(uid);
408 if (!ctl.pw)
24016335
SK
409 errx(EXIT_FAILURE, _("you (user %d) don't exist."),
410 uid);
5fe1c32f 411 ctl.username = ctl.pw->pw_name;
24016335 412 } else {
5fe1c32f
SK
413 ctl.pw = getpwnam(ctl.username);
414 if (!ctl.pw)
24016335 415 errx(EXIT_FAILURE, _("user \"%s\" does not exist."),
5fe1c32f 416 ctl.username);
24016335 417 }
5fe1c32f 418 parse_passwd(&ctl);
24016335 419#ifndef HAVE_LIBUSER
5fe1c32f 420 if (!(is_local(ctl.username)))
24016335
SK
421 errx(EXIT_FAILURE, _("can only change local entries"));
422#endif
423
424#ifdef HAVE_LIBSELINUX
425 if (is_selinux_enabled() > 0) {
426 if (uid == 0) {
5fe1c32f 427 if (checkAccess(ctl.username, PASSWD__CHFN) != 0) {
24016335
SK
428 security_context_t user_context;
429 if (getprevcon(&user_context) < 0)
430 user_context = NULL;
431 errx(EXIT_FAILURE,
432 _("%s is not authorized to change "
433 "the finger info of %s"),
434 user_context ? : _("Unknown user context"),
5fe1c32f 435 ctl.username);
24016335
SK
436 }
437 }
438 if (setupDefaultContext(_PATH_PASSWD))
439 errx(EXIT_FAILURE,
440 _("can't set default context for %s"), _PATH_PASSWD);
441 }
442#endif
443
444#ifdef HAVE_LIBUSER
445 /* If we're setuid and not really root, disallow the password change. */
5fe1c32f 446 if (geteuid() != getuid() && uid != ctl.pw->pw_uid) {
24016335 447#else
bf6c15ed 448 if (uid != 0 && uid != ctl.pw->pw_uid) {
24016335
SK
449#endif
450 errno = EACCES;
451 err(EXIT_FAILURE, _("running UID doesn't match UID of user we're "
452 "altering, change denied"));
453 }
454
5fe1c32f 455 printf(_("Changing finger information for %s.\n"), ctl.username);
24016335
SK
456
457#if !defined(HAVE_LIBUSER) && defined(CHFN_CHSH_PASSWORD)
5fe1c32f 458 if (!auth_pam("chfn", uid, ctl.username)) {
24016335
SK
459 return EXIT_FAILURE;
460 }
461#endif
462
d5fdba03 463 if (ctl.interactive)
5fe1c32f 464 ask_info(&ctl);
e88f0059
SK
465
466 add_missing(&ctl);
24016335 467
f723cbf5 468 if (!ctl.changed) {
24016335
SK
469 printf(_("Finger information not changed.\n"));
470 return EXIT_SUCCESS;
471 }
472
5fe1c32f 473 return save_new_data(&ctl) == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
24016335 474}