root/usr.bin/getent/getent.c
/*      $NetBSD: getent.c,v 1.21 2024/11/21 16:33:37 kre Exp $  */

/*-
 * Copyright (c) 2004-2006 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Luke Mewburn.
 *
 * 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.
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``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 FOUNDATION OR CONTRIBUTORS
 * 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>
#ifndef lint
__RCSID("$NetBSD: getent.c,v 1.21 2024/11/21 16:33:37 kre Exp $");
#endif /* not lint */

#include <sys/socket.h>

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <grp.h>
#include <limits.h>
#include <netdb.h>
#include <netgroup.h>
#include <pwd.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <paths.h>
#include <err.h>

#include <arpa/inet.h>
#include <arpa/nameser.h>

#include <net/if.h>
#include <net/if_ether.h>

#include <netinet/in.h>         /* for INET6_ADDRSTRLEN */

#include <rpc/rpcent.h>

#include <disktab.h>

static int      usage(void) __attribute__((__noreturn__));
static int      parsenum(const char *, unsigned long *);
static int      disktab(int, char *[]);
static int      gettytab(int, char *[]);
static int      ethers(int, char *[]);
static int      group(int, char *[]);
static int      hosts(int, char *[]);
static int      netgroup(int, char *[]);
static int      networks(int, char *[]);
static int      passwd(int, char *[]);
static int      printcap(int, char *[]);
static int      protocols(int, char *[]);
static int      rpc(int, char *[]);
static int      services(int, char *[]);
static int      shells(int, char *[]);

enum {
        RV_OK           = 0,
        RV_USAGE        = 1,
        RV_NOTFOUND     = 2,
        RV_NOENUM       = 3
};

static struct getentdb {
        const char      *name;
        int             (*callback)(int, char *[]);
} databases[] = {
        {       "disktab",      disktab,        },
        {       "ethers",       ethers,         },
        {       "gettytab",     gettytab,       },
        {       "group",        group,          },
        {       "hosts",        hosts,          },
        {       "netgroup",     netgroup,       },
        {       "networks",     networks,       },
        {       "passwd",       passwd,         },
        {       "printcap",     printcap,       },
        {       "protocols",    protocols,      },
        {       "rpc",          rpc,            },
        {       "services",     services,       },
        {       "shells",       shells,         },

        {       NULL,           NULL,           },
};


int
main(int argc, char *argv[])
{
        struct getentdb *curdb;

        setprogname(argv[0]);

        if (argc < 2)
                usage();
        for (curdb = databases; curdb->name != NULL; curdb++)
                if (strcmp(curdb->name, argv[1]) == 0)
                        return (*curdb->callback)(argc, argv);

        warn("Unknown database `%s'", argv[1]);
        usage();
        /* NOTREACHED */
}

static int
usage(void)
{
        struct getentdb *curdb;
        size_t i;

        (void)fprintf(stderr, "Usage: %s database [key ...]\n",
            getprogname());
        (void)fprintf(stderr, "\tdatabase may be one of:");
        for (i = 0, curdb = databases; curdb->name != NULL; curdb++, i++) {
                if (i % 7 == 0)
                        (void)fputs("\n\t\t", stderr);
                (void)fprintf(stderr, "%s%s", i % 7 == 0 ? "" : " ",
                    curdb->name);
        }
        (void)fprintf(stderr, "\n");
        exit(RV_USAGE);
        /* NOTREACHED */
}

static int
parsenum(const char *word, unsigned long *result)
{
        unsigned long   num;
        char            *ep;

        assert(word != NULL);
        assert(result != NULL);

        if (!isdigit((unsigned char)word[0]))
                return 0;
        errno = 0;
        num = strtoul(word, &ep, 10);
        if (num == ULONG_MAX && errno == ERANGE)
                return 0;
        if (*ep != '\0')
                return 0;
        *result = num;
        return 1;
}

/*
 * printfmtstrings --
 *      vprintf(format, ...),
 *      then the aliases (beginning with prefix, separated by sep),
 *      then a newline
 */
static __printflike(4, 5) void
printfmtstrings(char *strings[], const char *prefix, const char *sep,
    const char *fmt, ...)
{
        va_list         ap;
        const char      *curpref;
        size_t          i;

        va_start(ap, fmt);
        (void)vprintf(fmt, ap);
        va_end(ap);

        curpref = prefix;
        for (i = 0; strings[i] != NULL; i++) {
                (void)printf("%s%s", curpref, strings[i]);
                curpref = sep;
        }
        (void)printf("\n");
}


                /*
                 * ethers
                 */

static int
ethers(int argc, char *argv[])
{
        char            hostname[MAXHOSTNAMELEN + 1], *hp;
        struct ether_addr ea, *eap;
        int             i, rv;

        assert(argc > 1);
        assert(argv != NULL);

#define ETHERSPRINT     (void)printf("%-17s  %s\n", ether_ntoa(eap), hp)

        rv = RV_OK;
        if (argc == 2) {
                warnx("Enumeration not supported on ethers");
                rv = RV_NOENUM;
        } else {
                for (i = 2; i < argc; i++) {
                        if ((eap = ether_aton(argv[i])) == NULL) {
                                eap = &ea;
                                hp = argv[i];
                                if (ether_hostton(hp, eap) != 0) {
                                        rv = RV_NOTFOUND;
                                        break;
                                }
                        } else {
                                hp = hostname;
                                if (ether_ntohost(hp, eap) != 0) {
                                        rv = RV_NOTFOUND;
                                        break;
                                }
                        }
                        ETHERSPRINT;
                }
        }
        return rv;
}

                /*
                 * group
                 */

static int
group(int argc, char *argv[])
{
        struct group    *gr;
        unsigned long   id;
        int             i, rv;

        assert(argc > 1);
        assert(argv != NULL);

#define GROUPPRINT      printfmtstrings(gr->gr_mem, "", ",", "%s:%s:%u:", \
                            gr->gr_name, gr->gr_passwd, gr->gr_gid)

        (void)setgroupent(1);
        rv = RV_OK;
        if (argc == 2) {
                while ((gr = getgrent()) != NULL)
                        GROUPPRINT;
        } else {
                for (i = 2; i < argc; i++) {
                        if (parsenum(argv[i], &id))
                                gr = getgrgid((gid_t)id);
                        else
                                gr = getgrnam(argv[i]);
                        if (gr != NULL)
                                GROUPPRINT;
                        else {
                                rv = RV_NOTFOUND;
                                break;
                        }
                }
        }
        endgrent();
        return rv;
}


                /*
                 * hosts
                 */

static void
hostsprint(const struct hostent *he)
{
        char    buf[INET6_ADDRSTRLEN];

        assert(he != NULL);
        if (inet_ntop(he->h_addrtype, he->h_addr, buf, sizeof(buf)) == NULL)
                (void)strlcpy(buf, "# unknown", sizeof(buf));
        printfmtstrings(he->h_aliases, "  ", " ", "%-16s  %s", buf, he->h_name);
}

static int
hosts(int argc, char *argv[])
{
        struct hostent  *he;
        char            addr[IN6ADDRSZ];
        int             i, rv;

        assert(argc > 1);
        assert(argv != NULL);

        sethostent(1);
        rv = RV_OK;
        if (argc == 2) {
                while ((he = gethostent()) != NULL)
                        hostsprint(he);
        } else {
                for (i = 2; i < argc; i++) {
                        if (inet_pton(AF_INET6, argv[i], (void *)addr) > 0)
                                he = gethostbyaddr(addr, IN6ADDRSZ, AF_INET6);
                        else if (inet_pton(AF_INET, argv[i], (void *)addr) > 0)
                                he = gethostbyaddr(addr, INADDRSZ, AF_INET);
                        else
                                he = gethostbyname(argv[i]);
                        if (he != NULL)
                                hostsprint(he);
                        else {
                                rv = RV_NOTFOUND;
                                break;
                        }
                }
        }
        endhostent();
        return rv;
}

                /*
                 * netgroup
                 */
static int
netgroup(int argc, char *argv[])
{
        int             rv, i;
        bool            first;
        const char      *host, *user, *domain;

        assert(argc > 1);
        assert(argv != NULL);

#define NETGROUPPRINT(s)        (((s) != NULL) ? (s) : "")

        rv = RV_OK;
        if (argc == 2) {
                warnx("Enumeration not supported on netgroup");
                rv = RV_NOENUM;
        } else {
                for (i = 2; i < argc; i++) {
                        setnetgrent(argv[i]);
                        first = true;
                        while (getnetgrent(&host, &user, &domain) != 0) {
                                if (first) {
                                        first = false;
                                        (void)fputs(argv[i], stdout);
                                }
                                (void)printf(" (%s,%s,%s)",
                                    NETGROUPPRINT(host),
                                    NETGROUPPRINT(user),
                                    NETGROUPPRINT(domain));
                        }
                        if (!first)
                                (void)putchar('\n');
                        endnetgrent();
                }
        }

        return rv;
}

                /*
                 * networks
                 */

static void
networksprint(const struct netent *ne)
{
        char            buf[INET6_ADDRSTRLEN];
        struct  in_addr ianet;

        assert(ne != NULL);
        ianet = inet_makeaddr(ne->n_net, 0);
        if (inet_ntop(ne->n_addrtype, &ianet, buf, sizeof(buf)) == NULL)
                (void)strlcpy(buf, "# unknown", sizeof(buf));
        printfmtstrings(ne->n_aliases, "  ", " ", "%-16s  %s", ne->n_name, buf);
}

static int
networks(int argc, char *argv[])
{
        struct netent   *ne;
        in_addr_t       net;
        int             i, rv;

        assert(argc > 1);
        assert(argv != NULL);

        setnetent(1);
        rv = RV_OK;
        if (argc == 2) {
                while ((ne = getnetent()) != NULL)
                        networksprint(ne);
        } else {
                for (i = 2; i < argc; i++) {
                        net = inet_network(argv[i]);
                        if (net != INADDR_NONE)
                                ne = getnetbyaddr(net, AF_INET);
                        else
                                ne = getnetbyname(argv[i]);
                        if (ne != NULL)
                                networksprint(ne);
                        else {
                                rv = RV_NOTFOUND;
                                break;
                        }
                }
        }
        endnetent();
        return rv;
}


                /*
                 * passwd
                 */

static int
passwd(int argc, char *argv[])
{
        struct passwd   *pw;
        unsigned long   id;
        int             i, rv;

        assert(argc > 1);
        assert(argv != NULL);

#define PASSWDPRINT     (void)printf("%s:%s:%u:%u:%s:%s:%s\n", \
                            pw->pw_name, pw->pw_passwd, pw->pw_uid, \
                            pw->pw_gid, pw->pw_gecos, pw->pw_dir, pw->pw_shell)

        (void)setpassent(1);
        rv = RV_OK;
        if (argc == 2) {
                while ((pw = getpwent()) != NULL)
                        PASSWDPRINT;
        } else {
                for (i = 2; i < argc; i++) {
                        if (parsenum(argv[i], &id))
                                pw = getpwuid((uid_t)id);
                        else
                                pw = getpwnam(argv[i]);
                        if (pw != NULL)
                                PASSWDPRINT;
                        else {
                                rv = RV_NOTFOUND;
                                break;
                        }
                }
        }
        endpwent();
        return rv;
}

static char *
mygetent(const char * const * db_array, const char *name)
{
        char *buf = NULL;
        int error;

        switch (error = cgetent(&buf, db_array, name)) {
        case -3:
                warnx("tc= loop in record `%s' in `%s'", name, db_array[0]);
                break;
        case -2:
                warn("system error fetching record `%s' in `%s'", name,
                    db_array[0]);
                break;
        case -1:
        case 0:
                break;
        case 1:
                warnx("tc= reference not found in record for `%s' in `%s'",
                    name, db_array[0]);
                break;
        default:
                warnx("unknown error %d in record `%s' in `%s'", error, name,
                    db_array[0]);
                break;
        }
        return buf;
}

static char *
mygetone(const char * const * db_array, int first)
{
        char *buf = NULL;
        int error;

        switch (error = (first ? cgetfirst : cgetnext)(&buf, db_array)) {
        case -2:
                warnx("tc= loop in `%s'", db_array[0]);
                break;
        case -1:
                warn("system error fetching record in `%s'", db_array[0]);
                break;
        case 0:
        case 1:
                break;
        case 2:
                warnx("tc= reference not found in `%s'", db_array[0]);
                break;
        default:
                warnx("unknown error %d in `%s'", error, db_array[0]);
                break;
        }
        return buf;
}

static void
capprint(const char *cap)
{
        char *c = strchr(cap, ':');
        if (c)
                if (c == cap)
                        (void)printf("true\n");
                else {
                        int l = (int)(c - cap);
                        (void)printf("%*.*s\n", l, l, cap);
                }
        else
                (void)printf("%s\n", cap);
}

static void
prettyprint(char *b)
{
#define TERMWIDTH 65
        int did = 0;
        size_t len;
        char *s, c;

        for (;;) {
                len = strlen(b);
                if (len <= TERMWIDTH) {
done:
                        if (did)
                                printf("\t:");
                        printf("%s\n", b);
                        return;
                }
                for (s = b + TERMWIDTH; s > b && *s != ':'; s--)
                        continue;
                if (*s++ != ':')
                        goto done;
                c = *s;
                *s = '\0';
                if (did)
                        printf("\t:");
                did++;
                printf("%s\\\n", b);
                *s = c;
                b = s;
        }
}

static void
handleone(const char * const *db_array, char *b, int recurse, int pretty,
    int level)
{
        char *tc;

        if (level && pretty)
                printf("\n");
        if (pretty)
                prettyprint(b);
        else
                printf("%s\n", b);
        if (!recurse || cgetstr(b, "tc", &tc) <= 0)
                return;

        b = mygetent(db_array, tc);
        free(tc);

        if (b == NULL)
                return;

        handleone(db_array, b, recurse, pretty, ++level);
        free(b);
}

static int
handlecap(const char *db, int argc, char *argv[])
{
        static const char sfx[] = "=#:";
        const char *db_array[] = { db, NULL };
        char    *b, *cap;
        int     i, rv, c;
        size_t  j;
        int     expand = 1, recurse = 0, pretty = 0;

        assert(argc > 1);
        assert(argv != NULL);

        argc--;
        argv++;
        while ((c = getopt(argc, argv, "pnr")) != -1)
                switch (c) {
                case 'n':
                        expand = 0;
                        break;
                case 'r':
                        expand = 0;
                        recurse = 1;
                        break;
                case 'p':
                        pretty = 1;
                        break;
                default:
                        usage();
                        break;
                }

        argc -= optind;
        argv += optind;
        csetexpandtc(expand);
        rv = RV_OK;
        if (argc == 0) {
                for (b = mygetone(db_array, 1); b; b = mygetone(db_array, 0)) {
                        handleone(db_array, b, recurse, pretty, 0);
                        free(b);
                }
        } else {
                if ((b = mygetent(db_array, argv[0])) == NULL)
                        return RV_NOTFOUND;
                if (argc == 1)
                        handleone(db_array, b, recurse, pretty, 0);
                else {
                        for (i = 2; i < argc; i++) {
                                for (j = 0; j < sizeof(sfx) - 1; j++) {
                                        cap = cgetcap(b, argv[i], sfx[j]);
                                        if (cap) {
                                                capprint(cap);
                                                break;
                                        }
                                }
                                if (j == sizeof(sfx) - 1)
                                        printf("false\n");
                        }
                }
                free(b);
        }
        return rv;
}

                /*
                 * gettytab
                 */

static int
gettytab(int argc, char *argv[])
{
        return handlecap(_PATH_GETTYTAB, argc, argv);
}

                /*
                 * printcap
                 */

static int
printcap(int argc, char *argv[])
{
        return handlecap(_PATH_PRINTCAP, argc, argv);
}

                /*
                 * disktab
                 */

static int
disktab(int argc, char *argv[])
{
        return handlecap(_PATH_DISKTAB, argc, argv);
}

                /*
                 * protocols
                 */

static int
protocols(int argc, char *argv[])
{
        struct protoent *pe;
        unsigned long   id;
        int             i, rv;

        assert(argc > 1);
        assert(argv != NULL);

#define PROTOCOLSPRINT  printfmtstrings(pe->p_aliases, "  ", " ", \
                            "%-16s  %5d", pe->p_name, pe->p_proto)

        setprotoent(1);
        rv = RV_OK;
        if (argc == 2) {
                while ((pe = getprotoent()) != NULL)
                        PROTOCOLSPRINT;
        } else {
                for (i = 2; i < argc; i++) {
                        if (parsenum(argv[i], &id))
                                pe = getprotobynumber((int)id);
                        else
                                pe = getprotobyname(argv[i]);
                        if (pe != NULL)
                                PROTOCOLSPRINT;
                        else {
                                rv = RV_NOTFOUND;
                                break;
                        }
                }
        }
        endprotoent();
        return rv;
}

                /*
                 * rpc
                 */

static int
rpc(int argc, char *argv[])
{
        struct rpcent   *re;
        unsigned long   id;
        int             i, rv;

        assert(argc > 1);
        assert(argv != NULL);

#define RPCPRINT        printfmtstrings(re->r_aliases, "  ", " ", \
                                "%-16s  %6d", \
                                re->r_name, re->r_number)

        setrpcent(1);
        rv = RV_OK;
        if (argc == 2) {
                while ((re = getrpcent()) != NULL)
                        RPCPRINT;
        } else {
                for (i = 2; i < argc; i++) {
                        if (parsenum(argv[i], &id))
                                re = getrpcbynumber((int)id);
                        else
                                re = getrpcbyname(argv[i]);
                        if (re != NULL)
                                RPCPRINT;
                        else {
                                rv = RV_NOTFOUND;
                                break;
                        }
                }
        }
        endrpcent();
        return rv;
}

                /*
                 * services
                 */

static int
services(int argc, char *argv[])
{
        struct servent  *se;
        unsigned long   id;
        char            *proto;
        int             i, rv;

        assert(argc > 1);
        assert(argv != NULL);

#define SERVICESPRINT   printfmtstrings(se->s_aliases, "  ", " ", \
                            "%-16s  %5d/%s", \
                            se->s_name, ntohs(se->s_port), se->s_proto)

        setservent(1);
        rv = RV_OK;
        if (argc == 2) {
                while ((se = getservent()) != NULL)
                        SERVICESPRINT;
        } else {
                for (i = 2; i < argc; i++) {
                        proto = strchr(argv[i], '/');
                        if (proto != NULL)
                                *proto++ = '\0';
                        if (parsenum(argv[i], &id))
                                se = getservbyport(htons(id), proto);
                        else
                                se = getservbyname(argv[i], proto);
                        if (se != NULL)
                                SERVICESPRINT;
                        else {
                                rv = RV_NOTFOUND;
                                break;
                        }
                }
        }
        endservent();
        return rv;
}


                /*
                 * shells
                 */

static int
shells(int argc, char *argv[])
{
        const char      *sh;
        int             i, rv;

        assert(argc > 1);
        assert(argv != NULL);

#define SHELLSPRINT     (void)printf("%s\n", sh)

        setusershell();
        rv = RV_OK;
        if (argc == 2) {
                while ((sh = getusershell()) != NULL)
                        SHELLSPRINT;
        } else {
                for (i = 2; i < argc; i++) {
                        setusershell();
                        while ((sh = getusershell()) != NULL) {
                                if (strcmp(sh, argv[i]) == 0) {
                                        SHELLSPRINT;
                                        break;
                                }
                        }
                        if (sh == NULL) {
                                rv = RV_NOTFOUND;
                                break;
                        }
                }
        }
        endusershell();
        return rv;
}