From 7279ff37f36077faa44a32540569c1a848bdba5e Mon Sep 17 00:00:00 2001 From: nekral-guest Date: Mon, 31 Dec 2007 13:43:04 +0000 Subject: [PATCH] * New function: process_flags() split out of main(). The flags variables are now global. * New functions: check_perms(), update_gecos(), get_old_fields(), and check_fields() split out of main(). * Before pam_end(), the return value of the previous pam API was already checked. No need to validate it again. --- ChangeLog | 9 ++ src/chfn.c | 444 ++++++++++++++++++++++++++++++----------------------- 2 files changed, 265 insertions(+), 188 deletions(-) diff --git a/ChangeLog b/ChangeLog index 7278a44e..dd8ace44 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2007-12-31 Nicolas François + + * src/chfn.c: New function: process_flags() split out of main(). + The flags variables are now global. + * src/chfn.c: New functions: check_perms(), update_gecos(), + get_old_fields(), and check_fields() split out of main(). + * src/chfn.c: Before pam_end(), the return value of the previous + pam API was already checked. No need to validate it again. + 2007-12-31 Nicolas François * src/newusers.c: Compilation fix for PAM support (pamh needs to be diff --git a/src/chfn.c b/src/chfn.c index 68118065..da167f6b 100644 --- a/src/chfn.c +++ b/src/chfn.c @@ -63,6 +63,15 @@ static char workph[BUFSIZ]; static char homeph[BUFSIZ]; static char slop[BUFSIZ]; static int amroot; +/* Flags */ +static int fflg = 0; /* -f - set full name */ +static int rflg = 0; /* -r - set room number */ +static int wflg = 0; /* -w - set work phone number */ +static int hflg = 0; /* -h - set home phone number */ +static int oflg = 0; /* -o - set other information */ +#ifdef USE_PAM +static pam_handle_t *pamh = NULL; +#endif /* * External identifiers @@ -73,6 +82,10 @@ static void usage (void); static int may_change_field (int); static void new_fields (void); static char *copy_field (char *, char *, char *); +static void process_flags (int argc, char **argv); +static void check_perms (const struct passwd *pw); +static void update_gecos (const char *user, char *gecos); +static void get_old_fields (const char *gecos); /* * usage - print command line syntax and exit @@ -197,59 +210,13 @@ static char *copy_field (char *in, char *out, char *extra) } /* - * chfn - change a user's password file information - * - * This command controls the GECOS field information in the password - * file entry. - * - * The valid options are - * - * -f full name - * -r room number - * -w work phone number - * -h home phone number - * -o other information (*) + * process_flags - parse the command line options * - * (*) requires root permission to execute. + * It will not return if an error is encountered. */ -int main (int argc, char **argv) +static void process_flags (int argc, char **argv) { - char *cp; /* temporary character pointer */ - const struct passwd *pw; /* password file entry */ - struct passwd pwent; /* modified password file entry */ - char old_gecos[BUFSIZ]; /* buffer for old GECOS fields */ - char new_gecos[BUFSIZ]; /* buffer for new GECOS fields */ int flag; /* flag currently being processed */ - int fflg = 0; /* -f - set full name */ - int rflg = 0; /* -r - set room number */ - int wflg = 0; /* -w - set work phone number */ - int hflg = 0; /* -h - set home phone number */ - int oflg = 0; /* -o - set other information */ - char *user; - -#ifdef USE_PAM - pam_handle_t *pamh = NULL; - int retval; -#endif - - sanitize_env (); - setlocale (LC_ALL, ""); - bindtextdomain (PACKAGE, LOCALEDIR); - textdomain (PACKAGE); - - /* - * This command behaves different for root and non-root - * users. - */ - amroot = (getuid () == 0); - - /* - * Get the program name. The program name is used as a - * prefix to most error messages. - */ - Prog = Basename (argv[0]); - - OPENLOG ("chfn"); /* * The remaining arguments will be processed one by one and executed @@ -309,52 +276,23 @@ int main (int argc, char **argv) usage (); } } +} - /* - * Get the name of the user to check. It is either the command line - * name, or the name getlogin() returns. - */ - if (optind < argc) { - user = argv[optind]; - pw = xgetpwnam (user); - if (!pw) { - fprintf (stderr, _("%s: unknown user %s\n"), Prog, - user); - exit (E_NOPERM); - } - } else { - pw = get_my_pwent (); - if (!pw) { - fprintf (stderr, - _ - ("%s: Cannot determine your user name.\n"), - Prog); - exit (E_NOPERM); - } - user = xstrdup (pw->pw_name); - } - -#ifdef USE_NIS - /* - * Now we make sure this is a LOCAL password entry for this user ... - */ - if (__ispwNIS ()) { - char *nis_domain; - char *nis_master; - - fprintf (stderr, - _("%s: cannot change user '%s' on NIS client.\n"), - Prog, user); - - if (!yp_get_default_domain (&nis_domain) && - !yp_master (nis_domain, "passwd.byname", &nis_master)) { - fprintf (stderr, - _ - ("%s: '%s' is the NIS master for this client.\n"), - Prog, nis_master); - } - exit (E_NOPERM); - } +/* + * check_perms - check if the caller is allowed to add a group + * + * Non-root users are only allowed to change their gecos field. + * (see also may_change_field()) + * + * Non-root users must be authenticated. + * + * It will not return if the user is not allowed. + */ +static void check_perms (const struct passwd *pw) +{ +#ifdef USE_PAM + int retval; + struct passwd *pampw; #endif /* @@ -393,17 +331,13 @@ int main (int argc, char **argv) #else /* !USE_PAM */ retval = PAM_SUCCESS; - { - struct passwd *pampw; - pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */ - if (pampw == NULL) { - retval = PAM_USER_UNKNOWN; - } + pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */ + if (pampw == NULL) { + retval = PAM_USER_UNKNOWN; + } - if (retval == PAM_SUCCESS) { - retval = pam_start ("chfn", pampw->pw_name, - &conv, &pamh); - } + if (retval == PAM_SUCCESS) { + retval = pam_start ("chfn", pampw->pw_name, &conv, &pamh); } if (retval == PAM_SUCCESS) { @@ -425,12 +359,117 @@ int main (int argc, char **argv) exit (E_NOPERM); } #endif /* USE_PAM */ +} + +/* + * update_gecos - update the gecos fields in the password database + * + * Commit the user's entry after changing her gecos field. + */ +static void update_gecos (const char *user, char *gecos) +{ + const struct passwd *pw; /* The user's password file entry */ + struct passwd pwent; /* modified password file entry */ + + /* + * Before going any further, raise the ulimit to prevent colliding + * into a lowered ulimit, and set the real UID to root to protect + * against unexpected signals. Any keyboard signals are set to be + * ignored. + */ + if (setuid (0)) { + fprintf (stderr, _("Cannot change ID to root.\n")); + SYSLOG ((LOG_ERR, "can't setuid(0)")); + closelog (); + exit (E_NOPERM); + } + pwd_init (); + + /* + * The passwd entry is now ready to be committed back to the + * password file. Get a lock on the file and open it. + */ + if (!pw_lock ()) { + fprintf (stderr, + _ + ("Cannot lock the password file; try again later.\n")); + SYSLOG ((LOG_WARN, "can't lock /etc/passwd")); + closelog (); + exit (E_NOPERM); + } + if (!pw_open (O_RDWR)) { + fprintf (stderr, _("Cannot open the password file.\n")); + pw_unlock (); + SYSLOG ((LOG_ERR, "can't open /etc/passwd")); + closelog (); + exit (E_NOPERM); + } + + /* + * Get the entry to update using pw_locate() - we want the real one + * from /etc/passwd, not the one from getpwnam() which could contain + * the shadow password if (despite the warnings) someone enables + * AUTOSHADOW (or SHADOW_COMPAT in libc). --marekm + */ + pw = pw_locate (user); + if (!pw) { + pw_unlock (); + fprintf (stderr, + _("%s: %s not found in /etc/passwd\n"), Prog, user); + exit (E_NOPERM); + } + + /* + * Make a copy of the entry, then change the gecos field. The other + * fields remain unchanged. + */ + pwent = *pw; + pwent.pw_gecos = gecos; + + /* + * Update the passwd file entry. If there is a DBM file, update that + * entry as well. + */ + if (!pw_update (&pwent)) { + fprintf (stderr, _("Error updating the password entry.\n")); + pw_unlock (); + SYSLOG ((LOG_ERR, "error updating passwd entry")); + closelog (); + exit (E_NOPERM); + } + + /* + * Changes have all been made, so commit them and unlock the file. + */ + if (!pw_close ()) { + fprintf (stderr, _("Cannot commit password file changes.\n")); + pw_unlock (); + SYSLOG ((LOG_ERR, "can't rewrite /etc/passwd")); + closelog (); + exit (E_NOPERM); + } + if (!pw_unlock ()) { + fprintf (stderr, _("Cannot unlock the password file.\n")); + SYSLOG ((LOG_ERR, "can't unlock /etc/passwd")); + closelog (); + exit (E_NOPERM); + } +} + +/* + * get_old_fields - parse the old gecos and use the old value for the fields + * which are not set on the command line + */ +static void get_old_fields (const char *gecos) +{ + char *cp; /* temporary character pointer */ + char old_gecos[BUFSIZ]; /* buffer for old GECOS fields */ + STRFCPY (old_gecos, gecos); /* * Now get the full name. It is the first comma separated field in * the GECOS field. */ - STRFCPY (old_gecos, pw->pw_gecos); cp = copy_field (old_gecos, fflg ? (char *) 0 : fullnm, slop); /* @@ -461,19 +500,15 @@ int main (int argc, char **argv) strcat (slop, cp); } +} - /* - * If none of the fields were changed from the command line, let the - * user interactively change them. - */ - if (!fflg && !rflg && !wflg && !hflg && !oflg) { - printf (_("Changing the user information for %s\n"), user); - new_fields (); - } - - /* - * Check all of the fields for valid information - */ +/* + * check_fields - check all of the fields for valid information + * + * It will not return if a field is not valid. + */ +static void check_fields (void) +{ if (valid_field (fullnm, ":,=")) { fprintf (stderr, _("%s: invalid name: '%s'\n"), Prog, fullnm); closelog (); @@ -504,110 +539,143 @@ int main (int argc, char **argv) closelog (); exit (E_NOPERM); } +} + +/* + * chfn - change a user's password file information + * + * This command controls the GECOS field information in the password + * file entry. + * + * The valid options are + * + * -f full name + * -r room number + * -w work phone number + * -h home phone number + * -o other information (*) + * + * (*) requires root permission to execute. + */ +int main (int argc, char **argv) +{ + const struct passwd *pw; /* password file entry */ + char new_gecos[BUFSIZ]; /* buffer for new GECOS fields */ + char *user; + + sanitize_env (); + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); /* - * Build the new GECOS field by plastering all the pieces together, - * if they will fit ... + * This command behaves different for root and non-root + * users. */ - if (strlen (fullnm) + strlen (roomno) + strlen (workph) + - strlen (homeph) + strlen (slop) > (unsigned int) 80) { - fprintf (stderr, _("%s: fields too long\n"), Prog); - closelog (); - exit (E_NOPERM); - } - snprintf (new_gecos, sizeof new_gecos, "%s,%s,%s,%s%s%s", - fullnm, roomno, workph, homeph, slop[0] ? "," : "", slop); + amroot = (getuid () == 0); /* - * Before going any further, raise the ulimit to prevent colliding - * into a lowered ulimit, and set the real UID to root to protect - * against unexpected signals. Any keyboard signals are set to be - * ignored. + * Get the program name. The program name is used as a + * prefix to most error messages. */ - if (setuid (0)) { - fprintf (stderr, _("Cannot change ID to root.\n")); - SYSLOG ((LOG_ERR, "can't setuid(0)")); - closelog (); - exit (E_NOPERM); - } - pwd_init (); + Prog = Basename (argv[0]); + + OPENLOG ("chfn"); + + /* parse the command line options */ + process_flags (argc, argv); /* - * The passwd entry is now ready to be committed back to the - * password file. Get a lock on the file and open it. + * Get the name of the user to check. It is either the command line + * name, or the name getlogin() returns. */ - if (!pw_lock ()) { - fprintf (stderr, - _ - ("Cannot lock the password file; try again later.\n")); - SYSLOG ((LOG_WARN, "can't lock /etc/passwd")); - closelog (); - exit (E_NOPERM); - } - if (!pw_open (O_RDWR)) { - fprintf (stderr, _("Cannot open the password file.\n")); - pw_unlock (); - SYSLOG ((LOG_ERR, "can't open /etc/passwd")); - closelog (); - exit (E_NOPERM); + if (optind < argc) { + user = argv[optind]; + pw = xgetpwnam (user); + if (!pw) { + fprintf (stderr, _("%s: unknown user %s\n"), Prog, + user); + exit (E_NOPERM); + } + } else { + pw = get_my_pwent (); + if (!pw) { + fprintf (stderr, + _ + ("%s: Cannot determine your user name.\n"), + Prog); + exit (E_NOPERM); + } + user = xstrdup (pw->pw_name); } +#ifdef USE_NIS /* - * Get the entry to update using pw_locate() - we want the real one - * from /etc/passwd, not the one from getpwnam() which could contain - * the shadow password if (despite the warnings) someone enables - * AUTOSHADOW (or SHADOW_COMPAT in libc). --marekm + * Now we make sure this is a LOCAL password entry for this user ... */ - pw = pw_locate (user); - if (!pw) { - pw_unlock (); + if (__ispwNIS ()) { + char *nis_domain; + char *nis_master; + fprintf (stderr, - _("%s: %s not found in /etc/passwd\n"), Prog, user); + _("%s: cannot change user '%s' on NIS client.\n"), + Prog, user); + + if (!yp_get_default_domain (&nis_domain) && + !yp_master (nis_domain, "passwd.byname", &nis_master)) { + fprintf (stderr, + _ + ("%s: '%s' is the NIS master for this client.\n"), + Prog, nis_master); + } exit (E_NOPERM); } +#endif + + /* Check that the caller is allowed to change the gecos of the + * specified user */ + check_perms (pw); + + /* If some fields were not set on the command line, load the value from + * the old gecos fields. */ + get_old_fields (pw->pw_gecos); /* - * Make a copy of the entry, then change the gecos field. The other - * fields remain unchanged. + * If none of the fields were changed from the command line, let the + * user interactively change them. */ - pwent = *pw; - pwent.pw_gecos = new_gecos; + if (!fflg && !rflg && !wflg && !hflg && !oflg) { + printf (_("Changing the user information for %s\n"), user); + new_fields (); + } /* - * Update the passwd file entry. If there is a DBM file, update that - * entry as well. + * Check all of the fields for valid information */ - if (!pw_update (&pwent)) { - fprintf (stderr, _("Error updating the password entry.\n")); - pw_unlock (); - SYSLOG ((LOG_ERR, "error updating passwd entry")); - closelog (); - exit (E_NOPERM); - } + check_fields (); /* - * Changes have all been made, so commit them and unlock the file. + * Build the new GECOS field by plastering all the pieces together, + * if they will fit ... */ - if (!pw_close ()) { - fprintf (stderr, _("Cannot commit password file changes.\n")); - pw_unlock (); - SYSLOG ((LOG_ERR, "can't rewrite /etc/passwd")); - closelog (); - exit (E_NOPERM); - } - if (!pw_unlock ()) { - fprintf (stderr, _("Cannot unlock the password file.\n")); - SYSLOG ((LOG_ERR, "can't unlock /etc/passwd")); + if (strlen (fullnm) + strlen (roomno) + strlen (workph) + + strlen (homeph) + strlen (slop) > (unsigned int) 80) { + fprintf (stderr, _("%s: fields too long\n"), Prog); closelog (); exit (E_NOPERM); } + snprintf (new_gecos, sizeof new_gecos, "%s,%s,%s,%s%s%s", + fullnm, roomno, workph, homeph, slop[0] ? "," : "", slop); + + /* Rewrite the user's gecos in the passwd file */ + update_gecos (user, new_gecos); + SYSLOG ((LOG_INFO, "changed user `%s' information", user)); nscd_flush_cache ("passwd"); #ifdef USE_PAM - if (retval == PAM_SUCCESS) - pam_end (pamh, PAM_SUCCESS); + pam_end (pamh, PAM_SUCCESS); #endif /* USE_PAM */ closelog (); -- 2.50.1