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