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"
44 #ifdef HAVE_LIBSELINUX
45 # include <selinux/selinux.h>
46 # include <selinux/av_permissions.h>
47 # include "selinux_utils.h"
51 # include <libuser/user.h>
53 #elif CHFN_CHSH_PASSWORD
67 /* we do not accept gecos field sizes longer than MAX_FIELD_SIZE */
68 #define MAX_FIELD_SIZE 256
70 static void __attribute__((__noreturn__
)) usage(FILE *fp
)
72 fputs(USAGE_HEADER
, fp
);
73 fprintf(fp
, _(" %s [options] [<username>]\n"), program_invocation_short_name
);
74 fputs(USAGE_OPTIONS
, fp
);
75 fputs(_(" -f, --full-name <full-name> real name\n"), fp
);
76 fputs(_(" -o, --office <office> office number\n"), fp
);
77 fputs(_(" -p, --office-phone <phone> office phone number\n"), fp
);
78 fputs(_(" -h, --home-phone <phone> home phone number\n"), fp
);
79 fputs(USAGE_SEPARATOR
, fp
);
80 fputs(_(" -u, --help display this help and exit\n"), fp
);
81 fputs(_(" -v, --version output version information and exit\n"), fp
);
82 fprintf(fp
, USAGE_MAN_TAIL("chfn(1)"));
83 exit(fp
== stderr
? EXIT_FAILURE
: EXIT_SUCCESS
);
87 * check_gecos_string () --
88 * check that the given gecos string is legal. if it's not legal,
89 * output "msg" followed by a description of the problem, and return (-1).
91 static int check_gecos_string(const char *msg
, char *gecos
)
94 const size_t len
= strlen(gecos
);
96 if (MAX_FIELD_SIZE
< len
) {
97 warnx(_("field %s is too long"), msg
);
100 for (i
= 0; i
< len
; i
++) {
102 if (c
== ',' || c
== ':' || c
== '=' || c
== '"' || c
== '\n') {
103 warnx(_("%s: '%c' is not allowed"), msg
, c
);
107 warnx(_("%s: control characters are not allowed"), msg
);
116 * parse the command line arguments.
117 * returns true if no information beyond the username was given.
119 static int parse_argv(int argc
, char *argv
[], struct finfo
*pinfo
)
121 int index
, c
, status
;
124 static struct option long_options
[] = {
125 {"full-name", required_argument
, 0, 'f'},
126 {"office", required_argument
, 0, 'o'},
127 {"office-phone", required_argument
, 0, 'p'},
128 {"home-phone", required_argument
, 0, 'h'},
129 {"help", no_argument
, 0, 'u'},
130 {"version", no_argument
, 0, 'v'},
131 {NULL
, no_argument
, 0, '0'},
137 c
= getopt_long(argc
, argv
, "f:r:p:h:o:uv", long_options
,
141 /* version? output version and exit. */
143 printf(UTIL_LINUX_VERSION
);
148 /* all other options must have an argument. */
151 /* ok, we were given an argument */
154 /* now store the argument */
157 pinfo
->full_name
= optarg
;
158 status
= check_gecos_string(_("Name"), optarg
);
161 pinfo
->office
= optarg
;
162 status
= check_gecos_string(_("Office"), optarg
);
165 pinfo
->office_phone
= optarg
;
166 status
= check_gecos_string(_("Office Phone"), optarg
);
169 pinfo
->home_phone
= optarg
;
170 status
= check_gecos_string(_("Home Phone"), optarg
);
178 /* done parsing arguments. check for a username. */
180 if (optind
+ 1 < argc
)
182 pinfo
->username
= argv
[optind
];
189 * take a struct password and fill in the fields of the struct finfo.
191 static void parse_passwd(struct passwd
*pw
, struct finfo
*pinfo
)
198 pinfo
->username
= pw
->pw_name
;
199 /* use pw_gecos - we take a copy since PAM destroys the original */
200 gecos
= xstrdup(pw
->pw_gecos
);
201 /* extract known fields */
202 pinfo
->full_name
= strsep(&gecos
, ",");
203 pinfo
->office
= strsep(&gecos
, ",");
204 pinfo
->office_phone
= strsep(&gecos
, ",");
205 pinfo
->home_phone
= strsep(&gecos
, ",");
206 /* extra fields contain site-specific information, and can
207 * not be changed by this version of chfn. */
208 pinfo
->other
= strsep(&gecos
, ",");
213 * ask the user for a given field and check that the string is legal.
215 static char *prompt(const char *question
, char *def_val
)
219 char buf
[MAX_FIELD_SIZE
+ 2];
224 printf("%s [%s]: ", question
, def_val
);
226 if (fgets(buf
, sizeof(buf
), stdin
) == NULL
)
227 errx(EXIT_FAILURE
, _("Aborted."));
229 /* remove white spaces from string end */
230 ltrim_whitespace((unsigned char *) ans
);
231 len
= rtrim_whitespace((unsigned char *) ans
);
234 if (!strcasecmp(ans
, "none"))
236 if (check_gecos_string(question
, ans
) >= 0)
244 * prompt the user for the finger information and store it.
246 static void ask_info(struct finfo
*oldfp
, struct finfo
*newfp
)
248 newfp
->full_name
= prompt(_("Name"), oldfp
->full_name
);
249 newfp
->office
= prompt(_("Office"), oldfp
->office
);
250 newfp
->office_phone
= prompt(_("Office Phone"), oldfp
->office_phone
);
251 newfp
->home_phone
= prompt(_("Home Phone"), oldfp
->home_phone
);
256 * set_changed_data () --
257 * incorporate the new data into the old finger info.
259 static int set_changed_data(struct finfo
*oldfp
, struct finfo
*newfp
)
263 if (newfp
->full_name
) {
264 oldfp
->full_name
= newfp
->full_name
;
268 oldfp
->office
= newfp
->office
;
271 if (newfp
->office_phone
) {
272 oldfp
->office_phone
= newfp
->office_phone
;
275 if (newfp
->home_phone
) {
276 oldfp
->home_phone
= newfp
->home_phone
;
284 * save_new_data () --
285 * save the given finger info in /etc/passwd.
286 * return zero on success.
288 static int save_new_data(struct finfo
*pinfo
)
293 /* null fields will confuse printf(). */
294 if (!pinfo
->full_name
)
295 pinfo
->full_name
= "";
298 if (!pinfo
->office_phone
)
299 pinfo
->office_phone
= "";
300 if (!pinfo
->home_phone
)
301 pinfo
->home_phone
= "";
305 /* create the new gecos string */
306 len
= xasprintf(&gecos
, "%s,%s,%s,%s,%s", pinfo
->full_name
, pinfo
->office
,
307 pinfo
->office_phone
, pinfo
->home_phone
, pinfo
->other
);
309 /* remove trailing empty fields (but not subfields of pinfo->other) */
310 if (!pinfo
->other
[0]) {
311 while (len
> 0 && gecos
[len
- 1] == ',')
317 if (set_value_libuser("chfn", pinfo
->pw
->pw_name
, pinfo
->pw
->pw_uid
,
318 LU_GECOS
, gecos
) < 0) {
319 #else /* HAVE_LIBUSER */
320 /* write the new struct passwd to the passwd file. */
321 pinfo
->pw
->pw_gecos
= gecos
;
322 if (setpwnam(pinfo
->pw
) < 0) {
323 warn("setpwnam failed");
326 ("Finger information *NOT* changed. Try again later.\n"));
330 printf(_("Finger information changed.\n"));
334 int main(int argc
, char **argv
)
337 struct finfo oldf
, newf
;
341 setlocale(LC_ALL
, ""); /* both for messages and for iscntrl() below */
342 bindtextdomain(PACKAGE
, LOCALEDIR
);
344 atexit(close_stdout
);
347 * "oldf" contains the users original finger information.
348 * "newf" contains the changed finger information, and contains NULL
349 * in fields that haven't been changed.
350 * in the end, "newf" is folded into "oldf".
352 * the reason the new finger information is not put _immediately_
353 * into "oldf" is that on the command line, new finger information
354 * can be specified before we know what user the information is
355 * being specified for.
358 memset(&oldf
, 0, sizeof(oldf
));
359 memset(&newf
, 0, sizeof(newf
));
361 interactive
= parse_argv(argc
, argv
, &newf
);
362 if (!newf
.username
) {
363 parse_passwd(getpwuid(uid
), &oldf
);
365 errx(EXIT_FAILURE
, _("you (user %d) don't exist."),
368 parse_passwd(getpwnam(newf
.username
), &oldf
);
370 errx(EXIT_FAILURE
, _("user \"%s\" does not exist."),
375 if (!(is_local(oldf
.username
)))
376 errx(EXIT_FAILURE
, _("can only change local entries"));
379 #ifdef HAVE_LIBSELINUX
380 if (is_selinux_enabled() > 0) {
382 if (checkAccess(oldf
.username
, PASSWD__CHFN
) != 0) {
383 security_context_t user_context
;
384 if (getprevcon(&user_context
) < 0)
387 _("%s is not authorized to change "
388 "the finger info of %s"),
389 user_context
? : _("Unknown user context"),
393 if (setupDefaultContext(_PATH_PASSWD
))
395 _("can't set default context for %s"), _PATH_PASSWD
);
400 /* If we're setuid and not really root, disallow the password change. */
401 if (geteuid() != getuid() && uid
!= oldf
.pw
->pw_uid
) {
403 if (uid
!= 0 && uid
!= oldf
.pw
->pw_uid
) {
406 err(EXIT_FAILURE
, _("running UID doesn't match UID of user we're "
407 "altering, change denied"));
410 printf(_("Changing finger information for %s.\n"), oldf
.username
);
412 #if !defined(HAVE_LIBUSER) && defined(CHFN_CHSH_PASSWORD)
413 if(!auth_pam("chfn", uid
, oldf
.username
)) {
419 ask_info(&oldf
, &newf
);
421 if (!set_changed_data(&oldf
, &newf
)) {
422 printf(_("Finger information not changed.\n"));
426 return save_new_data(&oldf
) == 0 ? EXIT_SUCCESS
: EXIT_FAILURE
;