]> git.ipfire.org Git - thirdparty/util-linux.git/blob - login-utils/chfn.c
chfn: simplify parse_passwd() by using strsep()
[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
44 #ifdef HAVE_LIBSELINUX
45 # include <selinux/selinux.h>
46 # include <selinux/av_permissions.h>
47 # include "selinux_utils.h"
48 #endif
49
50 #ifdef HAVE_LIBUSER
51 # include <libuser/user.h>
52 # include "libuser.h"
53 #elif CHFN_CHSH_PASSWORD
54 # include "auth.h"
55 #endif
56
57 struct finfo {
58 struct passwd *pw;
59 char *username;
60 char *full_name;
61 char *office;
62 char *office_phone;
63 char *home_phone;
64 char *other;
65 };
66
67 /* we do not accept gecos field sizes longer than MAX_FIELD_SIZE */
68 #define MAX_FIELD_SIZE 256
69
70 static void __attribute__((__noreturn__)) usage(FILE *fp)
71 {
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);
84 }
85
86 /*
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).
90 */
91 static int check_gecos_string(const char *msg, char *gecos)
92 {
93 unsigned int i, c;
94 const size_t len = strlen(gecos);
95
96 if (MAX_FIELD_SIZE < len) {
97 warnx(_("field %s is too long"), msg);
98 return -1;
99 }
100 for (i = 0; i < len; i++) {
101 c = gecos[i];
102 if (c == ',' || c == ':' || c == '=' || c == '"' || c == '\n') {
103 warnx(_("%s: '%c' is not allowed"), msg, c);
104 return -1;
105 }
106 if (iscntrl(c)) {
107 warnx(_("%s: control characters are not allowed"), msg);
108 return -1;
109 }
110 }
111 return 0;
112 }
113
114 /*
115 * parse_argv () --
116 * parse the command line arguments.
117 * returns true if no information beyond the username was given.
118 */
119 static int parse_argv(int argc, char *argv[], struct finfo *pinfo)
120 {
121 int index, c, status;
122 int info_given;
123
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'},
132 };
133
134 optind = 0;
135 info_given = false;
136 while (true) {
137 c = getopt_long(argc, argv, "f:r:p:h:o:uv", long_options,
138 &index);
139 if (c == -1)
140 break;
141 /* version? output version and exit. */
142 if (c == 'v') {
143 printf(UTIL_LINUX_VERSION);
144 exit(EXIT_SUCCESS);
145 }
146 if (c == 'u')
147 usage(stdout);
148 /* all other options must have an argument. */
149 if (!optarg)
150 usage(stderr);
151 /* ok, we were given an argument */
152 info_given = true;
153
154 /* now store the argument */
155 switch (c) {
156 case 'f':
157 pinfo->full_name = optarg;
158 status = check_gecos_string(_("Name"), optarg);
159 break;
160 case 'o':
161 pinfo->office = optarg;
162 status = check_gecos_string(_("Office"), optarg);
163 break;
164 case 'p':
165 pinfo->office_phone = optarg;
166 status = check_gecos_string(_("Office Phone"), optarg);
167 break;
168 case 'h':
169 pinfo->home_phone = optarg;
170 status = check_gecos_string(_("Home Phone"), optarg);
171 break;
172 default:
173 usage(stderr);
174 }
175 if (status != 0)
176 exit(EXIT_FAILURE);
177 }
178 /* done parsing arguments. check for a username. */
179 if (optind < argc) {
180 if (optind + 1 < argc)
181 usage(stderr);
182 pinfo->username = argv[optind];
183 }
184 return !info_given;
185 }
186
187 /*
188 * parse_passwd () --
189 * take a struct password and fill in the fields of the struct finfo.
190 */
191 static void parse_passwd(struct passwd *pw, struct finfo *pinfo)
192 {
193 char *gecos;
194
195 if (!pw)
196 return;
197 pinfo->pw = pw;
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, ",");
209 }
210
211 /*
212 * prompt () --
213 * ask the user for a given field and check that the string is legal.
214 */
215 static char *prompt(const char *question, char *def_val)
216 {
217 int len;
218 char *ans;
219 char buf[MAX_FIELD_SIZE + 2];
220
221 if (!def_val)
222 def_val = "";
223 while (true) {
224 printf("%s [%s]: ", question, def_val);
225 __fpurge(stdin);
226 if (fgets(buf, sizeof(buf), stdin) == NULL)
227 errx(EXIT_FAILURE, _("Aborted."));
228 ans = buf;
229 /* remove white spaces from string end */
230 ltrim_whitespace((unsigned char *) ans);
231 len = rtrim_whitespace((unsigned char *) ans);
232 if (len == 0)
233 return NULL;
234 if (!strcasecmp(ans, "none"))
235 return "";
236 if (check_gecos_string(question, ans) >= 0)
237 break;
238 }
239 return xstrdup(ans);
240 }
241
242 /*
243 * ask_info () --
244 * prompt the user for the finger information and store it.
245 */
246 static void ask_info(struct finfo *oldfp, struct finfo *newfp)
247 {
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);
252 printf("\n");
253 }
254
255 /*
256 * set_changed_data () --
257 * incorporate the new data into the old finger info.
258 */
259 static int set_changed_data(struct finfo *oldfp, struct finfo *newfp)
260 {
261 int changed = false;
262
263 if (newfp->full_name) {
264 oldfp->full_name = newfp->full_name;
265 changed = true;
266 }
267 if (newfp->office) {
268 oldfp->office = newfp->office;
269 changed = true;
270 }
271 if (newfp->office_phone) {
272 oldfp->office_phone = newfp->office_phone;
273 changed = true;
274 }
275 if (newfp->home_phone) {
276 oldfp->home_phone = newfp->home_phone;
277 changed = true;
278 }
279
280 return changed;
281 }
282
283 /*
284 * save_new_data () --
285 * save the given finger info in /etc/passwd.
286 * return zero on success.
287 */
288 static int save_new_data(struct finfo *pinfo)
289 {
290 char *gecos;
291 int len;
292
293 /* null fields will confuse printf(). */
294 if (!pinfo->full_name)
295 pinfo->full_name = "";
296 if (!pinfo->office)
297 pinfo->office = "";
298 if (!pinfo->office_phone)
299 pinfo->office_phone = "";
300 if (!pinfo->home_phone)
301 pinfo->home_phone = "";
302 if (!pinfo->other)
303 pinfo->other = "";
304
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);
308
309 /* remove trailing empty fields (but not subfields of pinfo->other) */
310 if (!pinfo->other[0]) {
311 while (len > 0 && gecos[len - 1] == ',')
312 len--;
313 gecos[len] = 0;
314 }
315
316 #ifdef HAVE_LIBUSER
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");
324 #endif
325 printf(_
326 ("Finger information *NOT* changed. Try again later.\n"));
327 return -1;
328 }
329 free(gecos);
330 printf(_("Finger information changed.\n"));
331 return 0;
332 }
333
334 int main(int argc, char **argv)
335 {
336 uid_t uid;
337 struct finfo oldf, newf;
338 int interactive;
339
340 sanitize_env();
341 setlocale(LC_ALL, ""); /* both for messages and for iscntrl() below */
342 bindtextdomain(PACKAGE, LOCALEDIR);
343 textdomain(PACKAGE);
344 atexit(close_stdout);
345
346 /*
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".
351 *
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.
356 */
357 uid = getuid();
358 memset(&oldf, 0, sizeof(oldf));
359 memset(&newf, 0, sizeof(newf));
360
361 interactive = parse_argv(argc, argv, &newf);
362 if (!newf.username) {
363 parse_passwd(getpwuid(uid), &oldf);
364 if (!oldf.username)
365 errx(EXIT_FAILURE, _("you (user %d) don't exist."),
366 uid);
367 } else {
368 parse_passwd(getpwnam(newf.username), &oldf);
369 if (!oldf.username)
370 errx(EXIT_FAILURE, _("user \"%s\" does not exist."),
371 newf.username);
372 }
373
374 #ifndef HAVE_LIBUSER
375 if (!(is_local(oldf.username)))
376 errx(EXIT_FAILURE, _("can only change local entries"));
377 #endif
378
379 #ifdef HAVE_LIBSELINUX
380 if (is_selinux_enabled() > 0) {
381 if (uid == 0) {
382 if (checkAccess(oldf.username, PASSWD__CHFN) != 0) {
383 security_context_t user_context;
384 if (getprevcon(&user_context) < 0)
385 user_context = NULL;
386 errx(EXIT_FAILURE,
387 _("%s is not authorized to change "
388 "the finger info of %s"),
389 user_context ? : _("Unknown user context"),
390 oldf.username);
391 }
392 }
393 if (setupDefaultContext(_PATH_PASSWD))
394 errx(EXIT_FAILURE,
395 _("can't set default context for %s"), _PATH_PASSWD);
396 }
397 #endif
398
399 #ifdef HAVE_LIBUSER
400 /* If we're setuid and not really root, disallow the password change. */
401 if (geteuid() != getuid() && uid != oldf.pw->pw_uid) {
402 #else
403 if (uid != 0 && uid != oldf.pw->pw_uid) {
404 #endif
405 errno = EACCES;
406 err(EXIT_FAILURE, _("running UID doesn't match UID of user we're "
407 "altering, change denied"));
408 }
409
410 printf(_("Changing finger information for %s.\n"), oldf.username);
411
412 #if !defined(HAVE_LIBUSER) && defined(CHFN_CHSH_PASSWORD)
413 if(!auth_pam("chfn", uid, oldf.username)) {
414 return EXIT_FAILURE;
415 }
416 #endif
417
418 if (interactive)
419 ask_info(&oldf, &newf);
420
421 if (!set_changed_data(&oldf, &newf)) {
422 printf(_("Finger information not changed.\n"));
423 return EXIT_SUCCESS;
424 }
425
426 return save_new_data(&oldf) == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
427 }