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