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>
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.
12 * $Date: 1998/06/11 22:30:11 $
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>
17 * Hacked by Peter Breitenlohner, peb@mppmu.mpg.de,
18 * to remove trailing empty fields. Oct 5, 96.
20 * 1999-02-22 Arkadiusz MiĆkiewicz <misiek@pld.ORG.PL>
21 * - added Native Language Support
32 #include <sys/types.h>
37 #include "closestream.h"
43 #include "logindefs.h"
45 #include "ch-common.h"
47 #ifdef HAVE_LIBSELINUX
48 # include <selinux/selinux.h>
49 # include "selinux_utils.h"
53 # include <libuser/user.h>
55 #elif CHFN_CHSH_PASSWORD
59 #ifdef HAVE_LIBREADLINE
60 # define _FUNCTION_DEF
61 # include <readline/readline.h>
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
;
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 */
89 /* we do not accept gecos field sizes longer than MAX_FIELD_SIZE */
90 #define MAX_FIELD_SIZE 256
92 static void __attribute__((__noreturn__
)) usage(void)
95 fputs(USAGE_HEADER
, fp
);
96 fprintf(fp
, _(" %s [options] [<username>]\n"), program_invocation_short_name
);
98 fputs(USAGE_SEPARATOR
, fp
);
99 fputs(_("Change your finger information.\n"), fp
);
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
);
107 printf( " -u, --help %s\n", USAGE_OPTSTR_HELP
);
108 printf( " -v, --version %s\n", USAGE_OPTSTR_VERSION
);
109 printf(USAGE_MAN_TAIL("chfn(1)"));
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).
118 static int check_gecos_string(const char *msg
, char *gecos
)
120 const size_t len
= strlen(gecos
);
122 if (MAX_FIELD_SIZE
< len
) {
123 warnx(_("field %s is too long"), msg
);
126 if (illegal_passwd_chars(gecos
)) {
127 warnx(_("%s: has illegal characters"), gecos
);
135 * parse the command line arguments.
136 * returns true if no information beyond the username was given.
138 static void parse_argv(struct chfn_control
*ctl
, int argc
, char **argv
)
140 int index
, c
, status
= 0;
141 static const struct option long_options
[] = {
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 },
151 while ((c
= getopt_long(argc
, argv
, "f:r:p:h:o:uv", long_options
,
155 if (!ctl
->allow_fullname
)
156 errx(EXIT_FAILURE
, _("login.defs forbids setting %s"), _("Name"));
157 ctl
->newf
.full_name
= optarg
;
158 status
+= check_gecos_string(_("Name"), optarg
);
161 if (!ctl
->allow_room
)
162 errx(EXIT_FAILURE
, _("login.defs forbids setting %s"), _("Office"));
163 ctl
->newf
.office
= optarg
;
164 status
+= check_gecos_string(_("Office"), optarg
);
167 if (!ctl
->allow_work
)
168 errx(EXIT_FAILURE
, _("login.defs forbids setting %s"), _("Office Phone"));
169 ctl
->newf
.office_phone
= optarg
;
170 status
+= check_gecos_string(_("Office Phone"), optarg
);
173 if (!ctl
->allow_home
)
174 errx(EXIT_FAILURE
, _("login.defs forbids setting %s"), _("Home Phone"));
175 ctl
->newf
.home_phone
= optarg
;
176 status
+= check_gecos_string(_("Home Phone"), optarg
);
179 print_version(EXIT_SUCCESS
);
183 errtryhelp(EXIT_FAILURE
);
186 ctl
->interactive
= 0;
190 /* done parsing arguments. check for a username. */
192 if (optind
+ 1 < argc
) {
193 warnx(_("cannot handle multiple usernames"));
194 errtryhelp(EXIT_FAILURE
);
196 ctl
->username
= argv
[optind
];
203 * take a struct password and fill in the fields of the struct finfo.
205 static void parse_passwd(struct chfn_control
*ctl
)
211 /* use pw_gecos - we take a copy since PAM destroys the original */
212 gecos
= xstrdup(ctl
->pw
->pw_gecos
);
213 /* extract known fields */
214 ctl
->oldf
.full_name
= strsep(&gecos
, ",");
215 ctl
->oldf
.office
= strsep(&gecos
, ",");
216 ctl
->oldf
.office_phone
= strsep(&gecos
, ",");
217 ctl
->oldf
.home_phone
= strsep(&gecos
, ",");
218 /* extra fields contain site-specific information, and can
219 * not be changed by this version of chfn. */
220 ctl
->oldf
.other
= strsep(&gecos
, ",");
224 * ask_new_field () --
225 * ask the user for a given field and check that the string is legal.
227 static char *ask_new_field(struct chfn_control
*ctl
, const char *question
,
232 #ifndef HAVE_LIBREADLINE
239 printf("%s [%s]: ", question
, def_val
);
241 #ifdef HAVE_LIBREADLINE
242 rl_bind_key('\t', rl_insert
);
243 if ((buf
= readline(NULL
)) == NULL
)
245 if (getline(&buf
, &dummy
, stdin
) < 0)
247 errx(EXIT_FAILURE
, _("Aborted."));
248 /* remove white spaces from string end */
249 ltrim_whitespace((unsigned char *) buf
);
250 len
= rtrim_whitespace((unsigned char *) buf
);
253 return xstrdup(def_val
);
255 if (!strcasecmp(buf
, "none")) {
260 if (check_gecos_string(question
, buf
) >= 0)
269 * find /etc/login.defs CHFN_RESTRICT and save restrictions to run time
271 static void get_login_defs(struct chfn_control
*ctl
)
277 /* real root does not have restrictions */
278 if (geteuid() == getuid() && getuid() == 0) {
279 ctl
->allow_fullname
= ctl
->allow_room
= ctl
->allow_work
= ctl
->allow_home
= 1;
282 s
= getlogindefs_str("CHFN_RESTRICT", "");
283 if (!strcmp(s
, "yes")) {
284 ctl
->allow_room
= ctl
->allow_work
= ctl
->allow_home
= 1;
287 if (!strcmp(s
, "no")) {
288 ctl
->allow_fullname
= ctl
->allow_room
= ctl
->allow_work
= ctl
->allow_home
= 1;
291 for (i
= 0; s
[i
]; i
++) {
294 ctl
->allow_fullname
= 1;
310 warnx(_("%s: CHFN_RESTRICT has unexpected value: %s"), _PATH_LOGINDEFS
, s
);
311 if (!ctl
->allow_fullname
&& !ctl
->allow_room
&& !ctl
->allow_work
&& !ctl
->allow_home
)
312 errx(EXIT_FAILURE
, _("%s: CHFN_RESTRICT does not allow any changes"), _PATH_LOGINDEFS
);
318 * prompt the user for the finger information and store it.
320 static void ask_info(struct chfn_control
*ctl
)
322 if (ctl
->allow_fullname
)
323 ctl
->newf
.full_name
= ask_new_field(ctl
, _("Name"), ctl
->oldf
.full_name
);
325 ctl
->newf
.office
= ask_new_field(ctl
, _("Office"), ctl
->oldf
.office
);
327 ctl
->newf
.office_phone
= ask_new_field(ctl
, _("Office Phone"), ctl
->oldf
.office_phone
);
329 ctl
->newf
.home_phone
= ask_new_field(ctl
, _("Home Phone"), ctl
->oldf
.home_phone
);
335 * find field value in uninteractive mode; can be new, old, or blank
337 static char *find_field(char *nf
, char *of
)
348 * add not supplied field values when in uninteractive mode
350 static void add_missing(struct chfn_control
*ctl
)
352 ctl
->newf
.full_name
= find_field(ctl
->newf
.full_name
, ctl
->oldf
.full_name
);
353 ctl
->newf
.office
= find_field(ctl
->newf
.office
, ctl
->oldf
.office
);
354 ctl
->newf
.office_phone
= find_field(ctl
->newf
.office_phone
, ctl
->oldf
.office_phone
);
355 ctl
->newf
.home_phone
= find_field(ctl
->newf
.home_phone
, ctl
->oldf
.home_phone
);
356 ctl
->newf
.other
= find_field(ctl
->newf
.other
, ctl
->oldf
.other
);
361 * save_new_data () --
362 * save the given finger info in /etc/passwd.
363 * return zero on success.
365 static int save_new_data(struct chfn_control
*ctl
)
370 /* create the new gecos string */
371 len
= xasprintf(&gecos
, "%s,%s,%s,%s,%s",
374 ctl
->newf
.office_phone
,
375 ctl
->newf
.home_phone
,
378 /* remove trailing empty fields (but not subfields of ctl->newf.other) */
379 if (!ctl
->newf
.other
|| !*ctl
->newf
.other
) {
380 while (len
> 0 && gecos
[len
- 1] == ',')
386 if (set_value_libuser("chfn", ctl
->username
, ctl
->pw
->pw_uid
,
387 LU_GECOS
, gecos
) < 0) {
388 #else /* HAVE_LIBUSER */
389 /* write the new struct passwd to the passwd file. */
390 ctl
->pw
->pw_gecos
= gecos
;
391 if (setpwnam(ctl
->pw
, ".chfn") < 0) {
392 warn("setpwnam failed");
395 ("Finger information *NOT* changed. Try again later.\n"));
399 printf(_("Finger information changed.\n"));
403 int main(int argc
, char **argv
)
406 struct chfn_control ctl
= {
411 setlocale(LC_ALL
, ""); /* both for messages and for iscntrl() below */
412 bindtextdomain(PACKAGE
, LOCALEDIR
);
414 close_stdout_atexit();
418 /* check /etc/login.defs CHFN_RESTRICT */
419 get_login_defs(&ctl
);
421 parse_argv(&ctl
, argc
, argv
);
423 ctl
.pw
= getpwuid(uid
);
425 errx(EXIT_FAILURE
, _("you (user %d) don't exist."),
427 ctl
.username
= ctl
.pw
->pw_name
;
429 ctl
.pw
= getpwnam(ctl
.username
);
431 errx(EXIT_FAILURE
, _("user \"%s\" does not exist."),
436 if (!(is_local(ctl
.username
)))
437 errx(EXIT_FAILURE
, _("can only change local entries"));
440 #ifdef HAVE_LIBSELINUX
441 if (is_selinux_enabled() > 0) {
443 access_vector_t av
= get_access_vector("passwd", "chfn");
445 if (selinux_check_passwd_access(av
) != 0) {
446 security_context_t user_context
;
447 if (getprevcon(&user_context
) < 0)
450 _("%s is not authorized to change "
451 "the finger info of %s"),
452 user_context
? : _("Unknown user context"),
456 if (setupDefaultContext(_PATH_PASSWD
))
458 _("can't set default context for %s"), _PATH_PASSWD
);
463 /* If we're setuid and not really root, disallow the password change. */
464 if (geteuid() != getuid() && uid
!= ctl
.pw
->pw_uid
) {
466 if (uid
!= 0 && uid
!= ctl
.pw
->pw_uid
) {
469 err(EXIT_FAILURE
, _("running UID doesn't match UID of user we're "
470 "altering, change denied"));
473 printf(_("Changing finger information for %s.\n"), ctl
.username
);
475 #if !defined(HAVE_LIBUSER) && defined(CHFN_CHSH_PASSWORD)
476 if (!auth_pam("chfn", uid
, ctl
.username
)) {
487 printf(_("Finger information not changed.\n"));
491 return save_new_data(&ctl
) == 0 ? EXIT_SUCCESS
: EXIT_FAILURE
;