]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - login-utils/chfn.c
libblkid: check status for the current CDROM slot
[thirdparty/util-linux.git] / login-utils / chfn.c
index 4746927c8240f02a955cea7702f55e47c6595315..b7395552bba90578fc74bf704f9b0d263ac6d30d 100644 (file)
 #include "setpwnam.h"
 #include "strutils.h"
 #include "xalloc.h"
+#include "logindefs.h"
+
+#include "ch-common.h"
 
 #ifdef HAVE_LIBSELINUX
 # include <selinux/selinux.h>
-# include <selinux/av_permissions.h>
 # include "selinux_utils.h"
 #endif
 
 # include "auth.h"
 #endif
 
+#ifdef HAVE_LIBREADLINE
+# define _FUNCTION_DEF
+# include <readline/readline.h>
+#endif
+
 struct finfo {
-       struct passwd *pw;
-       char *username;
        char *full_name;
        char *office;
        char *office_phone;
@@ -64,23 +69,45 @@ struct finfo {
        char *other;
 };
 
+struct chfn_control {
+       struct passwd *pw;
+       char *username;
+       /*  "oldf"  Contains the users original finger information.
+        *  "newf"  Contains the changed finger information, and contains
+        *          NULL in fields that haven't been changed.
+        *  In the end, "newf" is folded into "oldf".  */
+       struct finfo oldf, newf;
+       unsigned int
+               allow_fullname:1,       /* The login.defs restriction */
+               allow_room:1,              /* see: man login.defs(5) */
+               allow_work:1,              /* and look for CHFN_RESTRICT */
+               allow_home:1,              /* keyword for these four. */
+               changed:1,              /* is change requested */
+               interactive:1;          /* whether to prompt for fields or not */
+};
+
 /* we do not accept gecos field sizes longer than MAX_FIELD_SIZE */
 #define MAX_FIELD_SIZE         256
 
-static void __attribute__((__noreturn__)) usage(FILE *fp)
+static void __attribute__((__noreturn__)) usage(void)
 {
+       FILE *fp = stdout;
        fputs(USAGE_HEADER, fp);
        fprintf(fp, _(" %s [options] [<username>]\n"), program_invocation_short_name);
+
+       fputs(USAGE_SEPARATOR, fp);
+       fputs(_("Change your finger information.\n"), fp);
+
        fputs(USAGE_OPTIONS, fp);
        fputs(_(" -f, --full-name <full-name>  real name\n"), fp);
        fputs(_(" -o, --office <office>        office number\n"), fp);
        fputs(_(" -p, --office-phone <phone>   office phone number\n"), fp);
        fputs(_(" -h, --home-phone <phone>     home phone number\n"), fp);
        fputs(USAGE_SEPARATOR, fp);
-       fputs(USAGE_HELP, fp);
-       fputs(USAGE_VERSION, fp);
-       fprintf(fp, USAGE_MAN_TAIL("chfn(1)"));
-       exit(fp == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+       printf( " -u, --help                   %s\n", USAGE_OPTSTR_HELP);
+       printf( " -v, --version                %s\n", USAGE_OPTSTR_VERSION);
+       printf(USAGE_MAN_TAIL("chfn(1)"));
+       exit(EXIT_SUCCESS);
 }
 
 /*
@@ -90,23 +117,15 @@ static void __attribute__((__noreturn__)) usage(FILE *fp)
  */
 static int check_gecos_string(const char *msg, char *gecos)
 {
-       unsigned int i, c;
        const size_t len = strlen(gecos);
 
        if (MAX_FIELD_SIZE < len) {
                warnx(_("field %s is too long"), msg);
                return -1;
        }
-       for (i = 0; i < len; i++) {
-               c = gecos[i];
-               if (c == ',' || c == ':' || c == '=' || c == '"' || c == '\n') {
-                       warnx(_("%s: '%c' is not allowed"), msg, c);
-                       return -1;
-               }
-               if (iscntrl(c)) {
-                       warnx(_("%s: control characters are not allowed"), msg);
-                       return -1;
-               }
+       if (illegal_passwd_chars(gecos)) {
+               warnx(_("%s: has illegal characters"), gecos);
+               return -1;
        }
        return 0;
 }
@@ -116,189 +135,226 @@ static int check_gecos_string(const char *msg, char *gecos)
  *     parse the command line arguments.
  *     returns true if no information beyond the username was given.
  */
-static int parse_argv(int argc, char *argv[], struct finfo *pinfo)
+static void parse_argv(struct chfn_control *ctl, int argc, char **argv)
 {
-       int index, c, status;
-       int info_given;
-
-       static struct option long_options[] = {
-               {"full-name", required_argument, 0, 'f'},
-               {"office", required_argument, 0, 'o'},
-               {"office-phone", required_argument, 0, 'p'},
-               {"home-phone", required_argument, 0, 'h'},
-               {"help", no_argument, 0, 'u'},
-               {"version", no_argument, 0, 'v'},
-               {NULL, no_argument, 0, '0'},
+       int index, c, status = 0;
+       static const struct option long_options[] = {
+               { "full-name",    required_argument, NULL, 'f' },
+               { "office",       required_argument, NULL, 'o' },
+               { "office-phone", required_argument, NULL, 'p' },
+               { "home-phone",   required_argument, NULL, 'h' },
+               { "help",         no_argument,       NULL, 'u' },
+               { "version",      no_argument,       NULL, 'v' },
+               { NULL, 0, NULL, 0 },
        };
 
-       optind = 0;
-       info_given = false;
-       while (true) {
-               c = getopt_long(argc, argv, "f:r:p:h:o:uv", long_options,
-                               &index);
-               if (c == -1)
-                       break;
-               /* version?  output version and exit. */
-               if (c == 'v') {
-                       printf(UTIL_LINUX_VERSION);
-                       exit(EXIT_SUCCESS);
-               }
-               if (c == 'u')
-                       usage(stdout);
-               /* all other options must have an argument. */
-               if (!optarg)
-                       usage(stderr);
-               /* ok, we were given an argument */
-               info_given = true;
-
-               /* now store the argument */
+       while ((c = getopt_long(argc, argv, "f:r:p:h:o:uv", long_options,
+                               &index)) != -1) {
                switch (c) {
                case 'f':
-                       pinfo->full_name = optarg;
-                       status = check_gecos_string(_("Name"), optarg);
+                       if (!ctl->allow_fullname)
+                               errx(EXIT_FAILURE, _("login.defs forbids setting %s"), _("Name"));
+                       ctl->newf.full_name = optarg;
+                       status += check_gecos_string(_("Name"), optarg);
                        break;
                case 'o':
-                       pinfo->office = optarg;
-                       status = check_gecos_string(_("Office"), optarg);
+                       if (!ctl->allow_room)
+                               errx(EXIT_FAILURE, _("login.defs forbids setting %s"), _("Office"));
+                       ctl->newf.office = optarg;
+                       status += check_gecos_string(_("Office"), optarg);
                        break;
                case 'p':
-                       pinfo->office_phone = optarg;
-                       status = check_gecos_string(_("Office Phone"), optarg);
+                       if (!ctl->allow_work)
+                               errx(EXIT_FAILURE, _("login.defs forbids setting %s"), _("Office Phone"));
+                       ctl->newf.office_phone = optarg;
+                       status += check_gecos_string(_("Office Phone"), optarg);
                        break;
                case 'h':
-                       pinfo->home_phone = optarg;
-                       status = check_gecos_string(_("Home Phone"), optarg);
+                       if (!ctl->allow_home)
+                               errx(EXIT_FAILURE, _("login.defs forbids setting %s"), _("Home Phone"));
+                       ctl->newf.home_phone = optarg;
+                       status += check_gecos_string(_("Home Phone"), optarg);
                        break;
+               case 'v':
+                       print_version(EXIT_SUCCESS);
+               case 'u':
+                       usage();
                default:
-                       usage(stderr);
+                       errtryhelp(EXIT_FAILURE);
                }
-               if (status != 0)
-                       exit(EXIT_FAILURE);
+               ctl->changed = 1;
+               ctl->interactive = 0;
        }
+       if (status != 0)
+               exit(EXIT_FAILURE);
        /* done parsing arguments.  check for a username. */
        if (optind < argc) {
-               if (optind + 1 < argc)
-                       usage(stderr);
-               pinfo->username = argv[optind];
+               if (optind + 1 < argc) {
+                       warnx(_("cannot handle multiple usernames"));
+                       errtryhelp(EXIT_FAILURE);
+               }
+               ctl->username = argv[optind];
        }
-       return !info_given;
+       return;
 }
 
 /*
  *  parse_passwd () --
  *     take a struct password and fill in the fields of the struct finfo.
  */
-static void parse_passwd(struct passwd *pw, struct finfo *pinfo)
+static void parse_passwd(struct chfn_control *ctl)
 {
        char *gecos;
-       char *cp;
-
-       if (pw) {
-               pinfo->pw = pw;
-               pinfo->username = pw->pw_name;
-               /* use pw_gecos - we take a copy since PAM destroys the original */
-               gecos = xstrdup(pw->pw_gecos);
-               cp = (gecos ? gecos : "");
-               pinfo->full_name = cp;
-               cp = strchr(cp, ',');
-               if (cp) {
-                       *cp = 0, cp++;
-               } else
-                       return;
-               pinfo->office = cp;
-               cp = strchr(cp, ',');
-               if (cp) {
-                       *cp = 0, cp++;
-               } else
-                       return;
-               pinfo->office_phone = cp;
-               cp = strchr(cp, ',');
-               if (cp) {
-                       *cp = 0, cp++;
-               } else
-                       return;
-               pinfo->home_phone = cp;
-               /*  extra fields contain site-specific information, and can
-                *  not be changed by this version of chfn.  */
-               cp = strchr(cp, ',');
-               if (cp) {
-                       *cp = 0, cp++;
-               } else
-                       return;
-               pinfo->other = cp;
-       }
+
+       if (!ctl->pw)
+               return;
+       /* use pw_gecos - we take a copy since PAM destroys the original */
+       gecos = xstrdup(ctl->pw->pw_gecos);
+       /* extract known fields */
+       ctl->oldf.full_name = strsep(&gecos, ",");
+       ctl->oldf.office = strsep(&gecos, ",");
+       ctl->oldf.office_phone = strsep(&gecos, ",");
+       ctl->oldf.home_phone = strsep(&gecos, ",");
+       /*  extra fields contain site-specific information, and can
+        *  not be changed by this version of chfn.  */
+       ctl->oldf.other = strsep(&gecos, ",");
 }
 
 /*
- *  prompt () --
+ *  ask_new_field () --
  *     ask the user for a given field and check that the string is legal.
  */
-static char *prompt(const char *question, char *def_val)
+static char *ask_new_field(struct chfn_control *ctl, const char *question,
+                          char *def_val)
 {
        int len;
-       char *ans;
-       char buf[MAX_FIELD_SIZE + 2];
+       char *buf;
+#ifndef HAVE_LIBREADLINE
+       size_t dummy = 0;
+#endif
 
        if (!def_val)
                def_val = "";
        while (true) {
                printf("%s [%s]: ", question, def_val);
                __fpurge(stdin);
-               if (fgets(buf, sizeof(buf), stdin) == NULL)
+#ifdef HAVE_LIBREADLINE
+               rl_bind_key('\t', rl_insert);
+               if ((buf = readline(NULL)) == NULL)
+#else
+               if (getline(&buf, &dummy, stdin) < 0)
+#endif
                        errx(EXIT_FAILURE, _("Aborted."));
-               ans = buf;
                /* remove white spaces from string end */
-               ltrim_whitespace((unsigned char *) ans);
-               len = rtrim_whitespace((unsigned char *) ans);
-               if (len == 0)
-                       return NULL;
-               if (!strcasecmp(ans, "none"))
-                       return "";
-               if (check_gecos_string(question, ans) >= 0)
+               ltrim_whitespace((unsigned char *) buf);
+               len = rtrim_whitespace((unsigned char *) buf);
+               if (len == 0) {
+                       free(buf);
+                       return xstrdup(def_val);
+               }
+               if (!strcasecmp(buf, "none")) {
+                       free(buf);
+                       ctl->changed = 1;
+                       return xstrdup("");
+               }
+               if (check_gecos_string(question, buf) >= 0)
+                       break;
+       }
+       ctl->changed = 1;
+       return buf;
+}
+
+/*
+ *  get_login_defs()
+ *     find /etc/login.defs CHFN_RESTRICT and save restrictions to run time
+ */
+static void get_login_defs(struct chfn_control *ctl)
+{
+       const char *s;
+       size_t i;
+       int broken = 0;
+
+       /* real root does not have restrictions */
+       if (geteuid() == getuid() && getuid() == 0) {
+               ctl->allow_fullname = ctl->allow_room = ctl->allow_work = ctl->allow_home = 1;
+               return;
+       }
+       s = getlogindefs_str("CHFN_RESTRICT", "");
+       if (!strcmp(s, "yes")) {
+               ctl->allow_room = ctl->allow_work = ctl->allow_home = 1;
+               return;
+       }
+       if (!strcmp(s, "no")) {
+               ctl->allow_fullname = ctl->allow_room = ctl->allow_work = ctl->allow_home = 1;
+               return;
+       }
+       for (i = 0; s[i]; i++) {
+               switch (s[i]) {
+               case 'f':
+                       ctl->allow_fullname = 1;
+                       break;
+               case 'r':
+                       ctl->allow_room = 1;
+                       break;
+               case 'w':
+                       ctl->allow_work = 1;
                        break;
+               case 'h':
+                       ctl->allow_home = 1;
+                       break;
+               default:
+                       broken = 1;
+               }
        }
-       return xstrdup(ans);
+       if (broken)
+               warnx(_("%s: CHFN_RESTRICT has unexpected value: %s"), _PATH_LOGINDEFS, s);
+       if (!ctl->allow_fullname && !ctl->allow_room && !ctl->allow_work && !ctl->allow_home)
+               errx(EXIT_FAILURE, _("%s: CHFN_RESTRICT does not allow any changes"), _PATH_LOGINDEFS);
+       return;
 }
 
 /*
  *  ask_info () --
  *     prompt the user for the finger information and store it.
  */
-static void ask_info(struct finfo *oldfp, struct finfo *newfp)
+static void ask_info(struct chfn_control *ctl)
 {
-       newfp->full_name = prompt(_("Name"), oldfp->full_name);
-       newfp->office = prompt(_("Office"), oldfp->office);
-       newfp->office_phone = prompt(_("Office Phone"), oldfp->office_phone);
-       newfp->home_phone = prompt(_("Home Phone"), oldfp->home_phone);
-       printf("\n");
+       if (ctl->allow_fullname)
+               ctl->newf.full_name = ask_new_field(ctl, _("Name"), ctl->oldf.full_name);
+       if (ctl->allow_room)
+               ctl->newf.office = ask_new_field(ctl, _("Office"), ctl->oldf.office);
+       if (ctl->allow_work)
+               ctl->newf.office_phone = ask_new_field(ctl, _("Office Phone"), ctl->oldf.office_phone);
+       if (ctl->allow_home)
+               ctl->newf.home_phone = ask_new_field(ctl, _("Home Phone"), ctl->oldf.home_phone);
+       putchar('\n');
 }
 
 /*
- *  set_changed_data () --
- *     incorporate the new data into the old finger info.
+ *  find_field () --
+ *     find field value in uninteractive mode; can be new, old, or blank
  */
-static int set_changed_data(struct finfo *oldfp, struct finfo *newfp)
+static char *find_field(char *nf, char *of)
 {
-       int changed = false;
-
-       if (newfp->full_name) {
-               oldfp->full_name = newfp->full_name;
-               changed = true;
-       }
-       if (newfp->office) {
-               oldfp->office = newfp->office;
-               changed = true;
-       }
-       if (newfp->office_phone) {
-               oldfp->office_phone = newfp->office_phone;
-               changed = true;
-       }
-       if (newfp->home_phone) {
-               oldfp->home_phone = newfp->home_phone;
-               changed = true;
-       }
+       if (nf)
+               return nf;
+       if (of)
+               return of;
+       return xstrdup("");
+}
 
-       return changed;
+/*
+ *  add_missing () --
+ *     add not supplied field values when in uninteractive mode
+ */
+static void add_missing(struct chfn_control *ctl)
+{
+       ctl->newf.full_name = find_field(ctl->newf.full_name, ctl->oldf.full_name);
+       ctl->newf.office = find_field(ctl->newf.office, ctl->oldf.office);
+       ctl->newf.office_phone = find_field(ctl->newf.office_phone, ctl->oldf.office_phone);
+       ctl->newf.home_phone = find_field(ctl->newf.home_phone, ctl->oldf.home_phone);
+       ctl->newf.other = find_field(ctl->newf.other, ctl->oldf.other);
+       printf("\n");
 }
 
 /*
@@ -306,41 +362,33 @@ static int set_changed_data(struct finfo *oldfp, struct finfo *newfp)
  *     save the given finger info in /etc/passwd.
  *     return zero on success.
  */
-static int save_new_data(struct finfo *pinfo)
+static int save_new_data(struct chfn_control *ctl)
 {
        char *gecos;
        int len;
 
-       /* null fields will confuse printf(). */
-       if (!pinfo->full_name)
-               pinfo->full_name = "";
-       if (!pinfo->office)
-               pinfo->office = "";
-       if (!pinfo->office_phone)
-               pinfo->office_phone = "";
-       if (!pinfo->home_phone)
-               pinfo->home_phone = "";
-       if (!pinfo->other)
-               pinfo->other = "";
-
        /* create the new gecos string */
-       len = xasprintf(&gecos, "%s,%s,%s,%s,%s", pinfo->full_name, pinfo->office,
-                 pinfo->office_phone, pinfo->home_phone, pinfo->other);
-
-       /* remove trailing empty fields (but not subfields of pinfo->other) */
-       if (!pinfo->other[0]) {
+       len = xasprintf(&gecos, "%s,%s,%s,%s,%s",
+                       ctl->newf.full_name,
+                       ctl->newf.office,
+                       ctl->newf.office_phone,
+                       ctl->newf.home_phone,
+                       ctl->newf.other);
+
+       /* remove trailing empty fields (but not subfields of ctl->newf.other) */
+       if (!ctl->newf.other || !*ctl->newf.other) {
                while (len > 0 && gecos[len - 1] == ',')
                        len--;
                gecos[len] = 0;
        }
 
 #ifdef HAVE_LIBUSER
-       if (set_value_libuser("chfn", pinfo->pw->pw_name, pinfo->pw->pw_uid,
+       if (set_value_libuser("chfn", ctl->username, ctl->pw->pw_uid,
                        LU_GECOS, gecos) < 0) {
 #else /* HAVE_LIBUSER */
        /* write the new struct passwd to the passwd file. */
-       pinfo->pw->pw_gecos = gecos;
-       if (setpwnam(pinfo->pw) < 0) {
+       ctl->pw->pw_gecos = gecos;
+       if (setpwnam(ctl->pw, ".chfn") < 0) {
                warn("setpwnam failed");
 #endif
                printf(_
@@ -355,52 +403,46 @@ static int save_new_data(struct finfo *pinfo)
 int main(int argc, char **argv)
 {
        uid_t uid;
-       struct finfo oldf, newf;
-       int interactive;
+       struct chfn_control ctl = {
+               .interactive = 1
+       };
 
        sanitize_env();
        setlocale(LC_ALL, "");  /* both for messages and for iscntrl() below */
        bindtextdomain(PACKAGE, LOCALEDIR);
        textdomain(PACKAGE);
-       atexit(close_stdout);
-
-       /*
-        *  "oldf" contains the users original finger information.
-        *  "newf" contains the changed finger information, and contains NULL
-        *         in fields that haven't been changed.
-        *  in the end, "newf" is folded into "oldf".
-        *
-        *  the reason the new finger information is not put _immediately_
-        *  into "oldf" is that on the command line, new finger information
-        *  can be specified before we know what user the information is
-        *  being specified for.
-        */
+       close_stdout_atexit();
+
        uid = getuid();
-       memset(&oldf, 0, sizeof(oldf));
-       memset(&newf, 0, sizeof(newf));
 
-       interactive = parse_argv(argc, argv, &newf);
-       if (!newf.username) {
-               parse_passwd(getpwuid(uid), &oldf);
-               if (!oldf.username)
+       /* check /etc/login.defs CHFN_RESTRICT */
+       get_login_defs(&ctl);
+
+       parse_argv(&ctl, argc, argv);
+       if (!ctl.username) {
+               ctl.pw = getpwuid(uid);
+               if (!ctl.pw)
                        errx(EXIT_FAILURE, _("you (user %d) don't exist."),
                             uid);
+               ctl.username = ctl.pw->pw_name;
        } else {
-               parse_passwd(getpwnam(newf.username), &oldf);
-               if (!oldf.username)
+               ctl.pw = getpwnam(ctl.username);
+               if (!ctl.pw)
                        errx(EXIT_FAILURE, _("user \"%s\" does not exist."),
-                            newf.username);
+                            ctl.username);
        }
-
+       parse_passwd(&ctl);
 #ifndef HAVE_LIBUSER
-       if (!(is_local(oldf.username)))
+       if (!(is_local(ctl.username)))
                errx(EXIT_FAILURE, _("can only change local entries"));
 #endif
 
 #ifdef HAVE_LIBSELINUX
        if (is_selinux_enabled() > 0) {
                if (uid == 0) {
-                       if (checkAccess(oldf.username, PASSWD__CHFN) != 0) {
+                       access_vector_t av = get_access_vector("passwd", "chfn");
+
+                       if (selinux_check_passwd_access(av) != 0) {
                                security_context_t user_context;
                                if (getprevcon(&user_context) < 0)
                                        user_context = NULL;
@@ -408,7 +450,7 @@ int main(int argc, char **argv)
                                     _("%s is not authorized to change "
                                       "the finger info of %s"),
                                     user_context ? : _("Unknown user context"),
-                                    oldf.username);
+                                    ctl.username);
                        }
                }
                if (setupDefaultContext(_PATH_PASSWD))
@@ -419,30 +461,32 @@ int main(int argc, char **argv)
 
 #ifdef HAVE_LIBUSER
        /* If we're setuid and not really root, disallow the password change. */
-       if (geteuid() != getuid() && uid != oldf.pw->pw_uid) {
+       if (geteuid() != getuid() && uid != ctl.pw->pw_uid) {
 #else
-       if (uid != 0 && uid != oldf.pw->pw_uid) {
+       if (uid != 0 && uid != ctl.pw->pw_uid) {
 #endif
                errno = EACCES;
                err(EXIT_FAILURE, _("running UID doesn't match UID of user we're "
                      "altering, change denied"));
        }
 
-       printf(_("Changing finger information for %s.\n"), oldf.username);
+       printf(_("Changing finger information for %s.\n"), ctl.username);
 
 #if !defined(HAVE_LIBUSER) && defined(CHFN_CHSH_PASSWORD)
-       if(!auth_pam("chfn", uid, oldf.username)) {
+       if (!auth_pam("chfn", uid, ctl.username)) {
                return EXIT_FAILURE;
        }
 #endif
 
-       if (interactive)
-               ask_info(&oldf, &newf);
+       if (ctl.interactive)
+               ask_info(&ctl);
+
+       add_missing(&ctl);
 
-       if (!set_changed_data(&oldf, &newf)) {
+       if (!ctl.changed) {
                printf(_("Finger information not changed.\n"));
                return EXIT_SUCCESS;
        }
 
-       return save_new_data(&oldf) == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+       return save_new_data(&ctl) == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
 }