]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - login-utils/lslogins.c
scriptreplay: cleanup usage()
[thirdparty/util-linux.git] / login-utils / lslogins.c
index 3ff772515dba9b4592028267de703e9d08071370..3d9c9b97a21b8f715c391726997a285df83208ec 100644 (file)
@@ -31,7 +31,7 @@
 #include <shadow.h>
 #include <paths.h>
 #include <time.h>
-#include <utmp.h>
+#include <utmpx.h>
 #include <signal.h>
 #include <err.h>
 #include <limits.h>
@@ -74,7 +74,7 @@ static int lslogins_flag;
 
 #define UL_UID_MIN 1000
 #define UL_UID_MAX 60000
-#define UL_SYS_UID_MIN 201
+#define UL_SYS_UID_MIN 101
 #define UL_SYS_UID_MAX 999
 
 /* we use the value of outmode to determine
@@ -115,6 +115,7 @@ struct lslogins_user {
        char *pwd_expire;
        char *pwd_ctime_min;
        char *pwd_ctime_max;
+       const char *pwd_method;
 
        char *last_login;
        char *last_tty;
@@ -166,6 +167,7 @@ enum {
        COL_PWDLOCK,
        COL_PWDEMPTY,
        COL_PWDDENY,
+       COL_PWDMETHOD,
        COL_GROUP,
        COL_GID,
        COL_SGROUPS,
@@ -219,6 +221,7 @@ static const struct lslogins_coldesc coldescs[] =
        [COL_PWDEMPTY]      = { "PWD-EMPTY",    N_("password not required"), N_("Password not required"), 1, SCOLS_FL_RIGHT },
        [COL_PWDDENY]       = { "PWD-DENY",     N_("login by password disabled"), N_("Login by password disabled"), 1, SCOLS_FL_RIGHT },
        [COL_PWDLOCK]       = { "PWD-LOCK",     N_("password defined, but locked"), N_("Password is locked"), 1, SCOLS_FL_RIGHT },
+       [COL_PWDMETHOD]     = { "PWD-METHOD",   N_("password encryption method"), N_("Password encryption method"), 0.1 },
        [COL_NOLOGIN]       = { "NOLOGIN",      N_("log in disabled by nologin(8) or pam_nologin(8)"), N_("No login"), 1, SCOLS_FL_RIGHT },
        [COL_GROUP]         = { "GROUP",        N_("primary group name"), N_("Primary group"), 0.1 },
        [COL_GID]           = { "GID",          N_("primary group ID"), "GID", 1, SCOLS_FL_RIGHT },
@@ -243,10 +246,10 @@ static const struct lslogins_coldesc coldescs[] =
 };
 
 struct lslogins_control {
-       struct utmp *wtmp;
+       struct utmpx *wtmp;
        size_t wtmp_size;
 
-       struct utmp *btmp;
+       struct utmpx *btmp;
        size_t btmp_size;
 
        void *usertree;
@@ -266,6 +269,7 @@ struct lslogins_control {
        const char *journal_path;
 
        unsigned int selinux_enabled : 1,
+                    fail_on_unknown : 1,               /* fail if user does not exist */
                     ulist_on : 1,
                     noheadings : 1,
                     notrunc : 1;
@@ -280,7 +284,7 @@ static struct libscols_table *tb;
  * column twice. That's enough, dynamically allocated array of the columns is
  * unnecessary overkill and over-engineering in this case */
 static int columns[ARRAY_SIZE(coldescs) * 2];
-static int ncolumns;
+static size_t ncolumns;
 
 static inline size_t err_columns_index(size_t arysz, size_t idx)
 {
@@ -333,11 +337,10 @@ static char *make_time(int mode, time_t time)
                                buf, sizeof(buf));
                break;
        case TIME_ISO:
-               rc = strtime_iso(&time, ISO_8601_DATE|ISO_8601_TIME|ISO_8601_TIMEZONE,
-                                  buf, sizeof(buf));
+               rc = strtime_iso(&time, ISO_TIMESTAMP_T, buf, sizeof(buf));
                break;
        case TIME_ISO_SHORT:
-               rc = strtime_iso(&time, ISO_8601_DATE, buf, sizeof(buf));
+               rc = strtime_iso(&time, ISO_DATE, buf, sizeof(buf));
                break;
        default:
                errx(EXIT_FAILURE, _("unsupported time type"));
@@ -410,19 +413,17 @@ again:
        return res;
 }
 
-static struct utmp *get_last_wtmp(struct lslogins_control *ctl, const char *username)
+static struct utmpx *get_last_wtmp(struct lslogins_control *ctl, const char *username)
 {
        size_t n = 0;
-       size_t len;
 
        if (!username)
                return NULL;
 
-       len = strlen(username);
        n = ctl->wtmp_size - 1;
        do {
                if (!strncmp(username, ctl->wtmp[n].ut_user,
-                   len < UT_NAMESIZE ? len : UT_NAMESIZE))
+                            sizeof(ctl->wtmp[0].ut_user)))
                        return ctl->wtmp + n;
        } while (n--);
        return NULL;
@@ -432,7 +433,7 @@ static struct utmp *get_last_wtmp(struct lslogins_control *ctl, const char *user
 static int require_wtmp(void)
 {
        size_t i;
-       for (i = 0; i < (size_t) ncolumns; i++)
+       for (i = 0; i < ncolumns; i++)
                if (is_wtmp_col(columns[i]))
                        return 1;
        return 0;
@@ -441,46 +442,44 @@ static int require_wtmp(void)
 static int require_btmp(void)
 {
        size_t i;
-       for (i = 0; i < (size_t) ncolumns; i++)
+       for (i = 0; i < ncolumns; i++)
                if (is_btmp_col(columns[i]))
                        return 1;
        return 0;
 }
 
-static struct utmp *get_last_btmp(struct lslogins_control *ctl, const char *username)
+static struct utmpx *get_last_btmp(struct lslogins_control *ctl, const char *username)
 {
        size_t n = 0;
-       size_t len;
 
        if (!username)
                return NULL;
 
-       len = strlen(username);
        n = ctl->btmp_size - 1;
        do {
                if (!strncmp(username, ctl->btmp[n].ut_user,
-                   len < UT_NAMESIZE ? len : UT_NAMESIZE))
+                            sizeof(ctl->wtmp[0].ut_user)))
                        return ctl->btmp + n;
        }while (n--);
        return NULL;
 
 }
 
-static int read_utmp(char const *file, size_t *nents, struct utmp **res)
+static int read_utmp(char const *file, size_t *nents, struct utmpx **res)
 {
        size_t n_read = 0, n_alloc = 0;
-       struct utmp *utmp = NULL, *u;
+       struct utmpx *utmp = NULL, *u;
 
-       if (utmpname(file) < 0)
+       if (utmpxname(file) < 0)
                return -errno;
 
-       setutent();
+       setutxent();
        errno = 0;
 
-       while ((u = getutent()) != NULL) {
+       while ((u = getutxent()) != NULL) {
                if (n_read == n_alloc) {
                        n_alloc += 32;
-                       utmp = xrealloc(utmp, n_alloc * sizeof (struct utmp));
+                       utmp = xrealloc(utmp, n_alloc * sizeof (struct utmpx));
                }
                utmp[n_read++] = *u;
        }
@@ -489,7 +488,7 @@ static int read_utmp(char const *file, size_t *nents, struct utmp **res)
                return -errno;
        }
 
-       endutent();
+       endutxent();
 
        *nents = n_read;
        *res = utmp;
@@ -566,14 +565,90 @@ static int get_nprocs(const uid_t uid)
        return nprocs;
 }
 
+static const char *get_pwd_method(const char *str, const char **next, unsigned int *sz)
+{
+       const char *p = str;
+       const char *res = NULL;
+
+       if (!p || *p++ != '$')
+               return NULL;
+
+       if (sz)
+               *sz = 0;
+
+       switch (*p) {
+       case '1':
+               res = "MD5";
+               if (sz)
+                       *sz = 22;
+               break;
+       case '2':
+               p++;
+               if (*p == 'a' || *p == 'y')
+                       res = "Blowfish";
+               break;
+       case '5':
+               res = "SHA-256";
+               if (sz)
+                       *sz = 43;
+               break;
+       case '6':
+               res = "SHA-512";
+               if (sz)
+                       *sz = 86;
+               break;
+       default:
+               return NULL;
+       }
+       p++;
+
+       if (*p != '$')
+               return NULL;
+       if (next)
+               *next = ++p;
+       return res;
+}
+
+#define is_valid_pwd_char(x)   (isalnum((unsigned char) (x)) || (x) ==  '.' || (x) == '/')
+
+/*
+ * This function do not accept empty passwords or locked accouns.
+ */
 static int valid_pwd(const char *str)
 {
-       const char *p;
+       const char *p = str;
+       unsigned int sz = 0, n;
 
-       for (p = str; p && *p; p++)
-               if (!isalnum((unsigned int) *p))
+       if (!str || !*str)
+               return 0;
+
+       /* $id$ */
+       if (get_pwd_method(str, &p, &sz) == NULL)
+               return 0;
+       if (!p || !*p)
+               return 0;
+
+       /* salt$ */
+       for (; *p; p++) {
+               if (*p == '$') {
+                       p++;
+                       break;
+               }
+               if (!is_valid_pwd_char(*p))
+                       return 0;
+       }
+       if (!*p)
+               return 0;
+
+       /* encrypted */
+       for (n = 0; *p; p++, n++) {
+               if (!is_valid_pwd_char(*p))
                        return 0;
-       return p > str ? 1 : 0;
+       }
+
+       if (sz && n != sz)
+               return 0;
+       return 1;
 }
 
 static struct lslogins_user *get_user_info(struct lslogins_control *ctl, const char *username)
@@ -582,12 +657,13 @@ static struct lslogins_user *get_user_info(struct lslogins_control *ctl, const c
        struct passwd *pwd;
        struct group *grp;
        struct spwd *shadow;
-       struct utmp *user_wtmp = NULL, *user_btmp = NULL;
-       int n = 0;
+       struct utmpx *user_wtmp = NULL, *user_btmp = NULL;
+       size_t n = 0;
        time_t time;
        uid_t uid;
        errno = 0;
 
+       errno = 0;
        pwd = username ? getpwnam(username) : getpwent();
        if (!pwd)
                return NULL;
@@ -610,12 +686,13 @@ static struct lslogins_user *get_user_info(struct lslogins_control *ctl, const c
                return NULL;
        }
 
-       user = xcalloc(1, sizeof(struct lslogins_user));
-
+       errno = 0;
        grp = getgrgid(pwd->pw_gid);
        if (!grp)
                return NULL;
 
+       user = xcalloc(1, sizeof(struct lslogins_user));
+
        if (ctl->wtmp)
                user_wtmp = get_last_wtmp(ctl, pwd->pw_name);
        if (ctl->btmp)
@@ -701,7 +778,6 @@ static struct lslogins_user *get_user_info(struct lslogins_control *ctl, const c
                        } else
                                user->pwd_deny = STATUS_UNKNOWN;
                        break;
-
                case COL_PWDLOCK:
                        if (shadow) {
                                if (*shadow->sp_pwdp == '!' && valid_pwd(shadow->sp_pwdp + 1))
@@ -709,6 +785,16 @@ static struct lslogins_user *get_user_info(struct lslogins_control *ctl, const c
                        } else
                                user->pwd_lock = STATUS_UNKNOWN;
                        break;
+               case COL_PWDMETHOD:
+                       if (shadow) {
+                               const char *p = shadow->sp_pwdp;
+
+                               if (*p == '!' || *p == '*')
+                                       p++;
+                               user->pwd_method = get_pwd_method(p, NULL, NULL);
+                       } else
+                               user->pwd_method = NULL;
+                       break;
                case COL_NOLOGIN:
                        if (strstr(pwd->pw_shell, "nologin"))
                                user->nologin = 1;
@@ -766,14 +852,6 @@ static struct lslogins_user *get_user_info(struct lslogins_control *ctl, const c
        return user;
 }
 
-/* some UNIX implementations set errno iff a passwd/grp/...
- * entry was not found. The original UNIX logins(1) utility always
- * ignores invalid login/group names, so we're going to as well.*/
-#define IS_REAL_ERRNO(e) !((e) == ENOENT || (e) == ESRCH || \
-               (e) == EBADF || (e) == EPERM || (e) == EAGAIN)
-
-/* get a definitive list of users we want info about... */
-
 static int str_to_uint(char *s, unsigned int *ul)
 {
        char *end;
@@ -785,6 +863,7 @@ static int str_to_uint(char *s, unsigned int *ul)
        return 1;
 }
 
+/* get a definitive list of users we want info about... */
 static int get_ulist(struct lslogins_control *ctl, char *logins, char *groups)
 {
        char *u, *g;
@@ -878,20 +957,25 @@ static struct lslogins_user *get_next_user(struct lslogins_control *ctl)
        return u;
 }
 
+/* some UNIX implementations set errno iff a passwd/grp/...
+ * entry was not found. The original UNIX logins(1) utility always
+ * ignores invalid login/group names, so we're going to as well.*/
+#define IS_REAL_ERRNO(e) !((e) == ENOENT || (e) == ESRCH || \
+               (e) == EBADF || (e) == EPERM || (e) == EAGAIN)
+
 static int get_user(struct lslogins_control *ctl, struct lslogins_user **user,
                    const char *username)
 {
        *user = get_user_info(ctl, username);
-       if (!*user)
-               if (IS_REAL_ERRNO(errno))
-                       return -1;
+       if (!*user && IS_REAL_ERRNO(errno))
+               return -1;
        return 0;
 }
 
 static int cmp_uid(const void *a, const void *b)
 {
-       uid_t x = ((struct lslogins_user *)a)->uid;
-       uid_t z = ((struct lslogins_user *)b)->uid;
+       uid_t x = ((const struct lslogins_user *)a)->uid;
+       uid_t z = ((const struct lslogins_user *)b)->uid;
        return x > z ? 1 : (x < z ? -1 : 0);
 }
 
@@ -901,12 +985,17 @@ static int create_usertree(struct lslogins_control *ctl)
        size_t n = 0;
 
        if (ctl->ulist_on) {
-               while (n < ctl->ulsiz) {
-                       if (get_user(ctl, &user, ctl->ulist[n]))
+               for (n = 0; n < ctl->ulsiz; n++) {
+                       int rc = get_user(ctl, &user, ctl->ulist[n]);
+
+                       if (ctl->fail_on_unknown && !user) {
+                               warnx(_("cannot found '%s'"), ctl->ulist[n]);
                                return -1;
-                       if (user) /* otherwise an invalid user name has probably been given */
-                               tsearch(user, &ctl->usertree, cmp_uid);
-                       ++n;
+                       }
+                       if (rc || !user)
+                               continue;
+
+                       tsearch(user, &ctl->usertree, cmp_uid);
                }
        } else {
                while ((user = get_next_user(ctl)))
@@ -918,10 +1007,10 @@ static int create_usertree(struct lslogins_control *ctl)
 static struct libscols_table *setup_table(struct lslogins_control *ctl)
 {
        struct libscols_table *table = scols_new_table();
-       int n = 0;
+       size_t n = 0;
 
        if (!table)
-               errx(EXIT_FAILURE, _("failed to initialize output table"));
+               err(EXIT_FAILURE, _("failed to allocate output table"));
        if (ctl->noheadings)
                scols_table_enable_noheadings(table, 1);
 
@@ -971,13 +1060,16 @@ fail:
 static void fill_table(const void *u, const VISIT which, const int depth __attribute__((unused)))
 {
        struct libscols_line *ln;
-       struct lslogins_user *user = *(struct lslogins_user **)u;
-       int n = 0;
+       const struct lslogins_user *user = *(struct lslogins_user * const *)u;
+       size_t n = 0;
 
        if (which == preorder || which == endorder)
                return;
 
        ln = scols_table_new_line(tb, NULL);
+       if (!ln)
+               err(EXIT_FAILURE, _("failed to allocate output line"));
+
        while (n < ncolumns) {
                int rc = 0;
 
@@ -1000,6 +1092,9 @@ static void fill_table(const void *u, const VISIT which, const int depth __attri
                case COL_PWDDENY:
                        rc = scols_line_set_data(ln, n, get_status(user->pwd_deny));
                        break;
+               case COL_PWDMETHOD:
+                       rc = scols_line_set_data(ln, n, user->pwd_method);
+                       break;
                case COL_GROUP:
                        rc = scols_line_set_data(ln, n, user->group);
                        break;
@@ -1073,8 +1168,8 @@ static void fill_table(const void *u, const VISIT which, const int depth __attri
                        err(EXIT_FAILURE, _("internal error: unknown column"));
                }
 
-               if (rc != 0)
-                       err(EXIT_FAILURE, _("failed to set data"));
+               if (rc)
+                       err(EXIT_FAILURE, _("failed to add output data"));
                ++n;
        }
        return;
@@ -1202,7 +1297,7 @@ static void free_user(void *f)
        free(u);
 }
 
-static int parse_time_mode(const char *optarg)
+static int parse_time_mode(const char *s)
 {
        struct lslogins_timefmt {
                const char *name;
@@ -1216,18 +1311,19 @@ static int parse_time_mode(const char *optarg)
        size_t i;
 
        for (i = 0; i < ARRAY_SIZE(timefmts); i++) {
-               if (strcmp(timefmts[i].name, optarg) == 0)
+               if (strcmp(timefmts[i].name, s) == 0)
                        return timefmts[i].val;
        }
-       errx(EXIT_FAILURE, _("unknown time format: %s"), optarg);
+       errx(EXIT_FAILURE, _("unknown time format: %s"), s);
 }
 
-static void __attribute__((__noreturn__)) usage(FILE *out)
+static void __attribute__((__noreturn__)) usage(void)
 {
+       FILE *out = stdout;
        size_t i;
 
        fputs(USAGE_HEADER, out);
-       fprintf(out, _(" %s [options]\n"), program_invocation_short_name);
+       fprintf(out, _(" %s [options] [<username>]\n"), program_invocation_short_name);
 
        fputs(USAGE_SEPARATOR, out);
        fputs(_("Display information about known users in the system.\n"), out);
@@ -1245,6 +1341,7 @@ static void __attribute__((__noreturn__)) usage(FILE *out)
        fputs(_("     --noheadings         don't print headings\n"), out);
        fputs(_("     --notruncate         don't truncate output\n"), out);
        fputs(_(" -o, --output[=<list>]    define the columns to output\n"), out);
+       fputs(_("     --output-all         output all columns\n"), out);
        fputs(_(" -p, --pwd                display information related to login by password.\n"), out);
        fputs(_(" -r, --raw                display in raw mode\n"), out);
        fputs(_(" -s, --system-accs        display system accounts\n"), out);
@@ -1255,24 +1352,21 @@ static void __attribute__((__noreturn__)) usage(FILE *out)
        fputs(_("     --wtmp-file <path>   set an alternate path for wtmp\n"), out);
        fputs(_("     --btmp-file <path>   set an alternate path for btmp\n"), out);
        fputs(USAGE_SEPARATOR, out);
-       fputs(USAGE_HELP, out);
-       fputs(USAGE_VERSION, out);
-
-       fprintf(out, _("\nAvailable columns:\n"));
+       printf(USAGE_HELP_OPTIONS(26));
 
+       fputs(USAGE_COLUMNS, out);
        for (i = 0; i < ARRAY_SIZE(coldescs); i++)
-               fprintf(out, " %14s  %s\n", coldescs[i].name,
-                               _(coldescs[i].help));
+               fprintf(out, " %14s  %s\n", coldescs[i].name, _(coldescs[i].help));
 
-       fprintf(out, USAGE_MAN_TAIL("lslogins(1)"));
+       printf(USAGE_MAN_TAIL("lslogins(1)"));
 
-       exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+       exit(EXIT_SUCCESS);
 }
 
 int main(int argc, char *argv[])
 {
-       int c, opt_o = 0;
-       char *logins = NULL, *groups = NULL;
+       int c;
+       char *logins = NULL, *groups = NULL, *outarg = NULL;
        char *path_wtmp = _PATH_WTMP, *path_btmp = _PATH_BTMP;
        struct lslogins_control *ctl = xcalloc(1, sizeof(struct lslogins_control));
        size_t i;
@@ -1284,6 +1378,7 @@ int main(int argc, char *argv[])
                OPT_NOTRUNC,
                OPT_NOHEAD,
                OPT_TIME_FMT,
+               OPT_OUTPUT_ALL,
        };
 
        static const struct option longopts[] = {
@@ -1299,6 +1394,7 @@ int main(int argc, char *argv[])
                { "notruncate",     no_argument,        0, OPT_NOTRUNC },
                { "noheadings",     no_argument,        0, OPT_NOHEAD },
                { "output",         required_argument,  0, 'o' },
+               { "output-all",     no_argument,        0, OPT_OUTPUT_ALL },
                { "last",           no_argument,        0, 'L', },
                { "raw",            no_argument,        0, 'r' },
                { "system-accs",    no_argument,        0, 's' },
@@ -1329,7 +1425,7 @@ int main(int argc, char *argv[])
        setlocale(LC_ALL, "");
        bindtextdomain(PACKAGE, LOCALEDIR);
        textdomain(PACKAGE);
-       atexit(close_stdout);
+       close_stdout_atexit();
 
        ctl->time_mode = TIME_SHORT;
 
@@ -1370,7 +1466,7 @@ int main(int argc, char *argv[])
                        groups = optarg;
                        break;
                case 'h':
-                       usage(stdout);
+                       usage();
                        break;
                case 'L':
                        add_column(columns, ncolumns++, COL_LAST_TTY);
@@ -1384,16 +1480,13 @@ int main(int argc, char *argv[])
                        outmode = OUT_NEWLINE;
                        break;
                case 'o':
-                       if (optarg) {
-                               if (*optarg == '=')
-                                       optarg++;
-                               ncolumns = string_to_idarray(optarg,
-                                               columns, ARRAY_SIZE(columns),
-                                               column_name_to_id);
-                               if (ncolumns < 0)
-                                       return EXIT_FAILURE;
-                       }
-                       opt_o = 1;
+                       if (*optarg == '=')
+                               optarg++;
+                       outarg = optarg;
+                       break;
+               case OPT_OUTPUT_ALL:
+                       for (ncolumns = 0; ncolumns < ARRAY_SIZE(coldescs); ncolumns++)
+                               columns[ncolumns] = ncolumns;
                        break;
                case 'r':
                        outmode = OUT_RAW;
@@ -1414,6 +1507,7 @@ int main(int argc, char *argv[])
                        add_column(columns, ncolumns++, COL_PWDDENY);
                        add_column(columns, ncolumns++, COL_NOLOGIN);
                        add_column(columns, ncolumns++, COL_HUSH_STATUS);
+                       add_column(columns, ncolumns++, COL_PWDMETHOD);
                        break;
                case 'z':
                        outmode = OUT_NUL;
@@ -1434,8 +1528,7 @@ int main(int argc, char *argv[])
                        ctl->time_mode = parse_time_mode(optarg);
                        break;
                case 'V':
-                       printf(UTIL_LINUX_VERSION);
-                       return EXIT_SUCCESS;
+                       print_version(EXIT_SUCCESS);
                case 'Z':
                {
 #ifdef HAVE_LIBSELINUX
@@ -1449,7 +1542,7 @@ int main(int argc, char *argv[])
                        break;
                }
                default:
-                       usage(stderr);
+                       errtryhelp(EXIT_FAILURE);
                }
        }
 
@@ -1458,6 +1551,7 @@ int main(int argc, char *argv[])
                        errx(EXIT_FAILURE, _("Only one user may be specified. Use -l for multiple users."));
                logins = argv[optind];
                outmode = OUT_PRETTY;
+               ctl->fail_on_unknown = 1;
        } else if (argc != optind)
                errx(EXIT_FAILURE, _("Only one user may be specified. Use -l for multiple users."));
 
@@ -1467,13 +1561,13 @@ int main(int argc, char *argv[])
        if (lslogins_flag & F_USRAC && lslogins_flag & F_SYSAC)
                lslogins_flag &= ~(F_USRAC | F_SYSAC);
 
-       if (outmode == OUT_PRETTY && !opt_o) {
+       if (outmode == OUT_PRETTY) {
                /* all columns for lslogins <username> */
                for (ncolumns = 0, i = 0; i < ARRAY_SIZE(coldescs); i++)
                         columns[ncolumns++] = i;
 
-       } else if (ncolumns == 2 && !opt_o) {
-               /* default colummns */
+       } else if (ncolumns == 2) {
+               /* default columns */
                add_column(columns, ncolumns++, COL_NPROCS);
                add_column(columns, ncolumns++, COL_PWDLOCK);
                add_column(columns, ncolumns++, COL_PWDDENY);
@@ -1481,6 +1575,10 @@ int main(int argc, char *argv[])
                add_column(columns, ncolumns++, COL_GECOS);
        }
 
+       if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns),
+                                        &ncolumns, column_name_to_id) < 0)
+               return EXIT_FAILURE;
+
        if (require_wtmp())
                parse_wtmp(ctl, path_wtmp);
        if (require_btmp())