root/usr.bin/logins/logins.c
/*-
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * Copyright (c) 2004 Dag-Erling Smørgrav
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer
 *    in this position and unchanged.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
#include <err.h>
#include <grp.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

struct xpasswd {
        char            *pw_name;
        char            *pw_passwd;
        uid_t            pw_uid;
        gid_t            pw_gid;
        time_t           pw_change;
        char            *pw_class;
        char            *pw_gecos;
        char            *pw_dir;
        char            *pw_shell;
        time_t           pw_expire;
        int              pw_selected;
};

struct xgroup {
        char            *gr_name;
        char            *gr_passwd;
        gid_t            gr_gid;
        char            *gr_mem;
};

static int               everything = 1;
static int               a_flag;
static int               d_flag;
static const char       *g_args;
static const char       *l_args;
static int               m_flag;
static int               o_flag;
static int               p_flag;
static int               s_flag;
static int               t_flag;
static int               u_flag;
static int               x_flag;

static int
member(const char *elem, const char *list)
{
        char *p;
        int len;

        p = strstr(list, elem);
        len = strlen(elem);

        return (p != NULL &&
            (p == list || p[-1] == ',') &&
            (p[len] == '\0' || p[len] == ','));
}

static void *
xmalloc(size_t size)
{
        void *newptr;

        if ((newptr = malloc(size)) == NULL)
                err(1, "malloc()");
        return (newptr);
}

static void *
xrealloc(void *ptr, size_t size)
{
        void *newptr;

        if ((newptr = realloc(ptr, size)) == NULL)
                err(1, "realloc()");
        return (newptr);
}

static char *
xstrdup(const char *str)
{
        char *dupstr;

        if ((dupstr = strdup(str)) == NULL)
                err(1, "strdup()");
        return (dupstr);
}

static struct xgroup    *grps;
static size_t            grpsz;
static size_t            ngrps;

static void
get_groups(void)
{
        struct group *grp;
        size_t len;
        int i;

        setgrent();
        for (;;) {
                if (ngrps == grpsz) {
                        grpsz += grpsz ? grpsz : 128;
                        grps = xrealloc(grps, grpsz * sizeof *grps);
                }
                if ((grp = getgrent()) == NULL)
                        break;
                grps[ngrps].gr_name = xstrdup(grp->gr_name);
                grps[ngrps].gr_passwd = xstrdup(grp->gr_passwd);
                grps[ngrps].gr_gid = grp->gr_gid;
                grps[ngrps].gr_mem = xstrdup("");
                for (i = 0, len = 1; grp->gr_mem[i] != NULL; ++i)
                        len += strlen(grp->gr_mem[i]) + 1;
                grps[ngrps].gr_mem = xmalloc(len);
                for (i = 0, len = 0; grp->gr_mem[i] != NULL; ++i)
                        len += sprintf(grps[ngrps].gr_mem + len,
                            i ? ",%s" : "%s", grp->gr_mem[i]);
                grps[ngrps].gr_mem[len] = '\0';
                ngrps++;
        }
        endgrent();
}

static struct xgroup *
find_group_bygid(gid_t gid)
{
        unsigned int i;

        for (i = 0; i < ngrps; ++i)
                if (grps[i].gr_gid == gid)
                        return (&grps[i]);
        return (NULL);
}

#if 0
static struct xgroup *
find_group_byname(const char *name)
{
        unsigned int i;

        for (i = 0; i < ngrps; ++i)
                if (strcmp(grps[i].gr_name, name) == 0)
                        return (&grps[i]);
        return (NULL);
}
#endif

static struct xpasswd   *pwds;
static size_t            pwdsz;
static size_t            npwds;

static int
pwd_cmp_byname(const void *ap, const void *bp)
{
        const struct passwd *a = ap;
        const struct passwd *b = bp;

        return (strcmp(a->pw_name, b->pw_name));
}

static int
pwd_cmp_byuid(const void *ap, const void *bp)
{
        const struct passwd *a = ap;
        const struct passwd *b = bp;

        return (a->pw_uid - b->pw_uid);
}

static void
get_users(void)
{
        struct passwd *pwd;

        setpwent();
        for (;;) {
                if (npwds == pwdsz) {
                        pwdsz += pwdsz ? pwdsz : 128;
                        pwds = xrealloc(pwds, pwdsz * sizeof *pwds);
                }
                if ((pwd = getpwent()) == NULL)
                        break;
                pwds[npwds].pw_name = xstrdup(pwd->pw_name);
                pwds[npwds].pw_passwd = xstrdup(pwd->pw_passwd);
                pwds[npwds].pw_uid = pwd->pw_uid;
                pwds[npwds].pw_gid = pwd->pw_gid;
                pwds[npwds].pw_change = pwd->pw_change;
                pwds[npwds].pw_class = xstrdup(pwd->pw_class);
                pwds[npwds].pw_gecos = xstrdup(pwd->pw_gecos);
                pwds[npwds].pw_dir = xstrdup(pwd->pw_dir);
                pwds[npwds].pw_shell = xstrdup(pwd->pw_shell);
                pwds[npwds].pw_expire = pwd->pw_expire;
                pwds[npwds].pw_selected = 0;
                npwds++;
        }
        endpwent();
}

static void
select_users(void)
{
        unsigned int i, j;
        struct xgroup *grp;
        struct xpasswd *pwd;

        for (i = 0, pwd = pwds; i < npwds; ++i, ++pwd) {
                if (everything) {
                        pwd->pw_selected = 1;
                        continue;
                }
                if (d_flag)
                        if ((i > 0 && pwd->pw_uid == pwd[-1].pw_uid) ||
                            (i < npwds - 1 && pwd->pw_uid == pwd[1].pw_uid)) {
                                pwd->pw_selected = 1;
                                continue;
                        }
                if (g_args) {
                        for (j = 0, grp = grps; j < ngrps; ++j, ++grp) {
                                if (member(grp->gr_name, g_args) &&
                                    member(pwd->pw_name, grp->gr_mem)) {
                                        pwd->pw_selected = 1;
                                        break;
                                }
                        }
                        if (pwd->pw_selected)
                                continue;
                }
                if (l_args)
                        if (member(pwd->pw_name, l_args)) {
                                pwd->pw_selected = 1;
                                continue;
                        }
                if (p_flag)
                        if (pwd->pw_passwd[0] == '\0') {
                                pwd->pw_selected = 1;
                                continue;
                        }
                if (s_flag)
                        if (pwd->pw_uid < 1000 || pwd->pw_uid == 65534) {
                                pwd->pw_selected = 1;
                                continue;
                        }
                if (u_flag)
                        if (pwd->pw_uid >= 1000 && pwd->pw_uid != 65534) {
                                pwd->pw_selected = 1;
                                continue;
                        }
        }
}

static void
sort_users(void)
{
        if (t_flag)
                mergesort(pwds, npwds, sizeof *pwds, pwd_cmp_byname);
        else
                mergesort(pwds, npwds, sizeof *pwds, pwd_cmp_byuid);
}

static void
display_user(struct xpasswd *pwd)
{
        struct xgroup *grp;
        unsigned int i;
        char cbuf[16], ebuf[16];
        struct tm *tm;

        grp = find_group_bygid(pwd->pw_gid);
        printf(o_flag ? "%s:%ld:%s:%ld:%s" : "%-15s %-7ld %-15s %-7ld %s\n",
            pwd->pw_name, (long)pwd->pw_uid, grp ? grp->gr_name : "",
            (long)pwd->pw_gid, pwd->pw_gecos);
        if (m_flag) {
                for (i = 0, grp = grps; i < ngrps; ++i, ++grp) {
                        if (grp->gr_gid == pwd->pw_gid ||
                            !member(pwd->pw_name, grp->gr_mem))
                                continue;
                        printf(o_flag ? "%s:%s:%ld" : "%24s%-15s %-7ld\n",
                            "", grp->gr_name, (long)grp->gr_gid);
                }
        }
        if (x_flag) {
                printf(o_flag ? "%s:%s" : "%24s%s\n", "", pwd->pw_dir);
                printf(o_flag ? "%s:%s" : "%24s%s\n", "", pwd->pw_shell);
        }
        if (a_flag) {
                tm = gmtime(&pwd->pw_change);
                strftime(cbuf, sizeof(cbuf), pwd->pw_change ? "%F" : "0", tm);
                tm = gmtime(&pwd->pw_expire);
                strftime(ebuf, sizeof(ebuf), pwd->pw_expire ? "%F" : "0", tm);
                printf(o_flag ? "%s:%s:%s" : "%24s%s %s\n", "", cbuf, ebuf);
        }
        if (o_flag)
                printf("\n");
}

static void
list_users(void)
{
        struct xpasswd *pwd;
        unsigned int i;

        for (i = 0, pwd = pwds; i < npwds; ++i, ++pwd)
                if (pwd->pw_selected)
                        display_user(pwd);
}

static void
usage(void)
{
        fprintf(stderr, "usage: logins [-admopstux] [-g group] [-l login]\n");
        exit(1);
}

int
main(int argc, char * const argv[])
{
        int o;

        while ((o = getopt(argc, argv, "adg:l:mopstux")) != -1)
                switch (o) {
                case 'a':
                        a_flag = 1;
                        break;
                case 'd':
                        everything = 0;
                        d_flag = 1;
                        break;
                case 'g':
                        everything = 0;
                        g_args = optarg;
                        break;
                case 'l':
                        everything = 0;
                        l_args = optarg;
                        break;
                case 'm':
                        m_flag = 1;
                        break;
                case 'o':
                        o_flag = 1;
                        break;
                case 'p':
                        everything = 0;
                        p_flag = 1;
                        break;
                case 's':
                        everything = 0;
                        s_flag = 1;
                        break;
                case 't':
                        t_flag = 1;
                        break;
                case 'u':
                        everything = 0;
                        u_flag = 1;
                        break;
                case 'x':
                        x_flag = 1;
                        break;
                default:
                        usage();
                }

        argc -= optind;
        argv += optind;

        if (argc > 0)
                usage();

        get_groups();
        get_users();
        select_users();
        sort_users();
        list_users();
        exit(0);
}