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