root/lib/libc/net/getservent.c
/*-
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * Copyright (c) 1983, 1993
 *      The Regents of the University of California.  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.
 * 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. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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/param.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <db.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <netdb.h>
#include <nsswitch.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#ifdef YP
#include <rpc/rpc.h>
#include <rpcsvc/yp_prot.h>
#include <rpcsvc/ypclnt.h>
#endif
#include "namespace.h"
#include "reentrant.h"
#include "un-namespace.h"
#include "netdb_private.h"
#ifdef NS_CACHING
#include "nscache.h"
#endif
#include "nss_tls.h"

enum constants
{
        SETSERVENT              = 1,
        ENDSERVENT              = 2,
        SERVENT_STORAGE_INITIAL = 1 << 10, /* 1 KByte */
        SERVENT_STORAGE_MAX     = 1 << 20, /* 1 MByte */
};

struct servent_mdata
{
        enum nss_lookup_type how;
        int compat_mode;
};

static const ns_src defaultsrc[] = {
        { NSSRC_COMPAT, NS_SUCCESS },
        { NULL, 0 }
};

static int servent_unpack(char *, struct servent *, char **, size_t, int *);

/* files backend declarations */
struct files_state
{
        FILE *fp;
        int stayopen;

        int compat_mode_active;
};
static void files_endstate(void *);
NSS_TLS_HANDLING(files);

static int files_servent(void *, void *, va_list);
static int files_setservent(void *, void *, va_list);

/* db backend declarations */
struct db_state
{
        DB *db;
        int stayopen;
        int keynum;
};
static void db_endstate(void *);
NSS_TLS_HANDLING(db);

static int db_servent(void *, void *, va_list);
static int db_setservent(void *, void *, va_list);

#ifdef YP
/* nis backend declarations */
static  int     nis_servent(void *, void *, va_list);
static  int     nis_setservent(void *, void *, va_list);

struct nis_state
{
        int yp_stepping;
        char yp_domain[MAXHOSTNAMELEN];
        char *yp_key;
        int yp_keylen;
};
static void nis_endstate(void *);
NSS_TLS_HANDLING(nis);

static int nis_servent(void *, void *, va_list);
static int nis_setservent(void *, void *, va_list);
#endif

/* compat backend declarations */
static int compat_setservent(void *, void *, va_list);

/* get** wrappers for get**_r functions declarations */
struct servent_state {
        struct servent serv;
        char *buffer;
        size_t bufsize;
};
static  void    servent_endstate(void *);
NSS_TLS_HANDLING(servent);

struct key {
        const char *proto;
        union {
                const char *name;
                int port;
        };
};

static int wrap_getservbyname_r(struct key, struct servent *, char *, size_t,
    struct servent **);
static int wrap_getservbyport_r(struct key, struct servent *, char *, size_t,
    struct servent **);
static int wrap_getservent_r(struct key, struct servent *, char *, size_t,
    struct servent **);
static struct servent *getserv(int (*fn)(struct key, struct servent *, char *,
    size_t, struct servent **), struct key);

#ifdef NS_CACHING
static int serv_id_func(char *, size_t *, va_list, void *);
static int serv_marshal_func(char *, size_t *, void *, va_list, void *);
static int serv_unmarshal_func(char *, size_t, void *, va_list, void *);
#endif

static int
servent_unpack(char *p, struct servent *serv, char **aliases,
    size_t aliases_size, int *errnop)
{
        char *cp, **q, *endp;
        long l;

        if (*p == '#')
                return -1;

        memset(serv, 0, sizeof(struct servent));

        cp = strpbrk(p, "#\n");
        if (cp != NULL)
                *cp = '\0';
        serv->s_name = p;

        p = strpbrk(p, " \t");
        if (p == NULL)
                return -1;
        *p++ = '\0';
        while (*p == ' ' || *p == '\t')
                p++;
        cp = strpbrk(p, ",/");
        if (cp == NULL)
                return -1;

        *cp++ = '\0';
        l = strtol(p, &endp, 10);
        if (endp == p || *endp != '\0' || l < 0 || l > USHRT_MAX)
                return -1;
        serv->s_port = htons((in_port_t)l);
        serv->s_proto = cp;

        q = serv->s_aliases = aliases;
        cp = strpbrk(cp, " \t");
        if (cp != NULL)
                *cp++ = '\0';
        while (cp && *cp) {
                if (*cp == ' ' || *cp == '\t') {
                        cp++;
                        continue;
                }
                if (q < &aliases[aliases_size - 1]) {
                        *q++ = cp;
                } else {
                        *q = NULL;
                        *errnop = ERANGE;
                        return -1;
                }
                cp = strpbrk(cp, " \t");
                if (cp != NULL)
                        *cp++ = '\0';
        }
        *q = NULL;

        return 0;
}

static int
parse_result(struct servent *serv, char *buffer, size_t bufsize,
    char *resultbuf, size_t resultbuflen, int *errnop)
{
        char **aliases;
        int aliases_size;

        if (bufsize <= resultbuflen + _ALIGNBYTES + sizeof(char *)) {
                *errnop = ERANGE;
                return (NS_RETURN);
        }
        aliases = (char **)_ALIGN(&buffer[resultbuflen + 1]);
        aliases_size = (buffer + bufsize - (char *)aliases) / sizeof(char *);
        if (aliases_size < 1) {
                *errnop = ERANGE;
                return (NS_RETURN);
        }

        memcpy(buffer, resultbuf, resultbuflen);
        buffer[resultbuflen] = '\0';

        if (servent_unpack(buffer, serv, aliases, aliases_size, errnop) != 0)
                return ((*errnop == 0) ? NS_NOTFOUND : NS_RETURN);
        return (NS_SUCCESS);
}

/* files backend implementation */
static  void
files_endstate(void *p)
{
        FILE * f;

        if (p == NULL)
                return;

        f = ((struct files_state *)p)->fp;
        if (f != NULL)
                fclose(f);

        free(p);
}

/*
 * compat structures. compat and files sources functionalities are almost
 * equal, so they all are managed by files_servent function
 */
static int
files_servent(void *retval, void *mdata, va_list ap)
{
        static const ns_src compat_src[] = {
#ifdef YP
                { NSSRC_NIS, NS_SUCCESS },
#endif
                { NULL, 0 }
        };
        ns_dtab compat_dtab[] = {
                { NSSRC_DB, db_servent,
                        (void *)((struct servent_mdata *)mdata)->how },
#ifdef YP
                { NSSRC_NIS, nis_servent,
                        (void *)((struct servent_mdata *)mdata)->how },
#endif
                { NULL, NULL, NULL }
        };

        struct files_state *st;
        int rv;
        int stayopen;

        struct servent_mdata *serv_mdata;
        char *name;
        char *proto;
        int port;

        struct servent *serv;
        char *buffer;
        size_t bufsize;
        int *errnop;

        size_t linesize;
        char *line;
        char **cp;

        name = NULL;
        proto = NULL;
        serv_mdata = (struct servent_mdata *)mdata;
        switch (serv_mdata->how) {
        case nss_lt_name:
                name = va_arg(ap, char *);
                proto = va_arg(ap, char *);
                break;
        case nss_lt_id:
                port = va_arg(ap, int);
                proto = va_arg(ap, char *);
                break;
        case nss_lt_all:
                break;
        default:
                return NS_NOTFOUND;
        }

        serv = va_arg(ap, struct servent *);
        buffer  = va_arg(ap, char *);
        bufsize = va_arg(ap, size_t);
        errnop = va_arg(ap,int *);

        *errnop = files_getstate(&st);
        if (*errnop != 0)
                return (NS_UNAVAIL);

        if (st->fp == NULL)
                st->compat_mode_active = 0;

        if (st->fp == NULL && (st->fp = fopen(_PATH_SERVICES, "re")) == NULL) {
                *errnop = errno;
                return (NS_UNAVAIL);
        }

        if (serv_mdata->how == nss_lt_all)
                stayopen = 1;
        else {
                rewind(st->fp);
                stayopen = st->stayopen;
        }

        rv = NS_NOTFOUND;
        do {
                if (!st->compat_mode_active) {
                        if ((line = fgetln(st->fp, &linesize)) == NULL) {
                                *errnop = errno;
                                rv = NS_RETURN;
                                break;
                        }

                        if (*line=='+' && serv_mdata->compat_mode != 0)
                                st->compat_mode_active = 1;
                }

                if (st->compat_mode_active != 0) {
                        switch (serv_mdata->how) {
                        case nss_lt_name:
                                rv = nsdispatch(retval, compat_dtab,
                                    NSDB_SERVICES_COMPAT, "getservbyname_r",
                                    compat_src, name, proto, serv, buffer,
                                    bufsize, errnop);
                                break;
                        case nss_lt_id:
                                rv = nsdispatch(retval, compat_dtab,
                                    NSDB_SERVICES_COMPAT, "getservbyport_r",
                                    compat_src, port, proto, serv, buffer,
                                        bufsize, errnop);
                                break;
                        case nss_lt_all:
                                rv = nsdispatch(retval, compat_dtab,
                                    NSDB_SERVICES_COMPAT, "getservent_r",
                                    compat_src, serv, buffer, bufsize, errnop);
                                break;
                        }

                        if (!(rv & NS_TERMINATE) ||
                            serv_mdata->how != nss_lt_all)
                                st->compat_mode_active = 0;

                        continue;
                }

                rv = parse_result(serv, buffer, bufsize, line, linesize,
                    errnop);
                if (rv == NS_NOTFOUND)
                        continue;
                if (rv == NS_RETURN)
                        break;

                rv = NS_NOTFOUND;
                switch (serv_mdata->how) {
                case nss_lt_name:
                        if (strcmp(name, serv->s_name) == 0)
                                goto gotname;
                        for (cp = serv->s_aliases; *cp; cp++)
                                if (strcmp(name, *cp) == 0)
                                        goto gotname;

                        continue;
                gotname:
                        if (proto == NULL || strcmp(serv->s_proto, proto) == 0)
                                rv = NS_SUCCESS;
                        break;
                case nss_lt_id:
                        if (port != serv->s_port)
                                continue;

                        if (proto == NULL || strcmp(serv->s_proto, proto) == 0)
                                rv = NS_SUCCESS;
                        break;
                case nss_lt_all:
                        rv = NS_SUCCESS;
                        break;
                }

        } while (!(rv & NS_TERMINATE));

        if (!stayopen && st->fp != NULL) {
                fclose(st->fp);
                st->fp = NULL;
        }

        if ((rv == NS_SUCCESS) && (retval != NULL))
                *(struct servent **)retval=serv;

        return (rv);
}

static int
files_setservent(void *retval, void *mdata, va_list ap)
{
        struct files_state *st;
        int rv;
        int f;

        rv = files_getstate(&st);
        if (rv != 0)
                return (NS_UNAVAIL);

        switch ((enum constants)(uintptr_t)mdata) {
        case SETSERVENT:
                f = va_arg(ap,int);
                if (st->fp == NULL)
                        st->fp = fopen(_PATH_SERVICES, "re");
                else
                        rewind(st->fp);
                st->stayopen |= f;
                break;
        case ENDSERVENT:
                if (st->fp != NULL) {
                        fclose(st->fp);
                        st->fp = NULL;
                }
                st->stayopen = 0;
                break;
        default:
                break;
        }

        st->compat_mode_active = 0;
        return (NS_UNAVAIL);
}

/* db backend implementation */
static  void
db_endstate(void *p)
{
        DB *db;

        if (p == NULL)
                return;

        db = ((struct db_state *)p)->db;
        if (db != NULL)
                db->close(db);

        free(p);
}

static int
db_servent(void *retval, void *mdata, va_list ap)
{
        char buf[BUFSIZ];
        DBT key, data, *result;
        DB *db;

        struct db_state *st;
        int rv;
        int stayopen;

        enum nss_lookup_type how;
        char *name;
        char *proto;
        int port;

        struct servent *serv;
        char *buffer;
        size_t bufsize;
        int *errnop;

        name = NULL;
        proto = NULL;
        how = (enum nss_lookup_type)(uintptr_t)mdata;
        switch (how) {
        case nss_lt_name:
                name = va_arg(ap, char *);
                proto = va_arg(ap, char *);
                break;
        case nss_lt_id:
                port = va_arg(ap, int);
                proto = va_arg(ap, char *);
                break;
        case nss_lt_all:
                break;
        default:
                return NS_NOTFOUND;
        }

        serv = va_arg(ap, struct servent *);
        buffer  = va_arg(ap, char *);
        bufsize = va_arg(ap, size_t);
        errnop = va_arg(ap,int *);

        *errnop = db_getstate(&st);
        if (*errnop != 0)
                return (NS_UNAVAIL);

        if (how == nss_lt_all && st->keynum < 0)
                return (NS_NOTFOUND);

        if (st->db == NULL) {
                st->db = dbopen(_PATH_SERVICES_DB, O_RDONLY, 0, DB_HASH, NULL);
                if (st->db == NULL) {
                        *errnop = errno;
                        return (NS_UNAVAIL);
                }
        }

        stayopen = (how == nss_lt_all) ? 1 : st->stayopen;
        db = st->db;

        do {
                switch (how) {
                case nss_lt_name:
                        key.data = buf;
                        if (proto == NULL)
                                key.size = snprintf(buf, sizeof(buf),
                                    "\376%s", name);
                        else
                                key.size = snprintf(buf, sizeof(buf),
                                    "\376%s/%s", name, proto);
                        key.size++;
                        if (db->get(db, &key, &data, 0) != 0 ||
                            db->get(db, &data, &key, 0) != 0) {
                                rv = NS_NOTFOUND;
                                goto db_fin;
                        }
                        result = &key;
                        break;
                case nss_lt_id:
                        key.data = buf;
                        port = htons(port);
                        if (proto == NULL)
                                key.size = snprintf(buf, sizeof(buf),
                                    "\377%d", port);
                        else
                                key.size = snprintf(buf, sizeof(buf),
                                    "\377%d/%s", port, proto);
                        key.size++;
                        if (db->get(db, &key, &data, 0) != 0 ||
                            db->get(db, &data, &key, 0) != 0) {
                                rv = NS_NOTFOUND;
                                goto db_fin;
                        }
                        result = &key;
                        break;
                case nss_lt_all:
                        key.data = buf;
                        key.size = snprintf(buf, sizeof(buf), "%d",
                            st->keynum++);
                        key.size++;
                        if (db->get(db, &key, &data, 0) != 0) {
                                st->keynum = -1;
                                rv = NS_NOTFOUND;
                                goto db_fin;
                        }
                        result = &data;
                        break;
                }

                rv = parse_result(serv, buffer, bufsize, result->data,
                    result->size - 1, errnop);

        } while (!(rv & NS_TERMINATE) && how == nss_lt_all);

db_fin:
        if (!stayopen && st->db != NULL) {
                db->close(db);
                st->db = NULL;
        }

        if (rv == NS_SUCCESS && retval != NULL)
                *(struct servent **)retval = serv;

        return (rv);
}

static int
db_setservent(void *retval, void *mdata, va_list ap)
{
        DB *db;
        struct db_state *st;
        int rv;
        int f;

        rv = db_getstate(&st);
        if (rv != 0)
                return (NS_UNAVAIL);

        switch ((enum constants)(uintptr_t)mdata) {
        case SETSERVENT:
                f = va_arg(ap, int);
                st->stayopen |= f;
                st->keynum = 0;
                break;
        case ENDSERVENT:
                db = st->db;
                if (db != NULL) {
                        db->close(db);
                        st->db = NULL;
                }
                st->stayopen = 0;
                break;
        default:
                break;
        }

        return (NS_UNAVAIL);
}

/* nis backend implementation */
#ifdef YP
static  void
nis_endstate(void *p)
{
        if (p == NULL)
                return;

        free(((struct nis_state *)p)->yp_key);
        free(p);
}

static int
nis_servent(void *retval, void *mdata, va_list ap)
{
        char *resultbuf, *lastkey;
        int resultbuflen;
        char *buf;

        struct nis_state *st;
        int rv;

        enum nss_lookup_type how;
        char *name;
        char *proto;
        int port;

        struct servent *serv;
        char *buffer;
        size_t bufsize;
        int *errnop;

        name = NULL;
        proto = NULL;
        buf = NULL;
        how = (enum nss_lookup_type)(uintptr_t)mdata;
        switch (how) {
        case nss_lt_name:
                name = va_arg(ap, char *);
                proto = va_arg(ap, char *);
                break;
        case nss_lt_id:
                port = va_arg(ap, int);
                proto = va_arg(ap, char *);
                break;
        case nss_lt_all:
                break;
        default:
                return NS_NOTFOUND;
        }

        serv = va_arg(ap, struct servent *);
        buffer  = va_arg(ap, char *);
        bufsize = va_arg(ap, size_t);
        errnop = va_arg(ap, int *);

        *errnop = nis_getstate(&st);
        if (*errnop != 0)
                return (NS_UNAVAIL);

        if (st->yp_domain[0] == '\0') {
                if (getdomainname(st->yp_domain, sizeof st->yp_domain)) {
                        *errnop = errno;
                        return (NS_UNAVAIL);
                }
        }

        do {
                switch (how) {
                case nss_lt_name:
                        free(buf);
                        asprintf(&buf, "%s/%s", name, proto);
                        if (buf == NULL)
                                return (NS_TRYAGAIN);
                        if (yp_match(st->yp_domain, "services.byname", buf,
                            strlen(buf), &resultbuf, &resultbuflen)) {
                                rv = NS_NOTFOUND;
                                goto fin;
                        }
                        break;
                case nss_lt_id:
                        free(buf);
                        asprintf(&buf, "%d/%s", ntohs(port), proto);
                        if (buf == NULL)
                                return (NS_TRYAGAIN);

                        /*
                         * We have to be a little flexible
                         * here. Ideally you're supposed to have both
                         * a services.byname and a services.byport
                         * map, but some systems have only
                         * services.byname. FreeBSD cheats a little by
                         * putting the services.byport information in
                         * the same map as services.byname so that
                         * either case will work. We allow for both
                         * possibilities here: if there is no
                         * services.byport map, we try services.byname
                         * instead.
                         */
                        rv = yp_match(st->yp_domain, "services.byport", buf,
                            strlen(buf), &resultbuf, &resultbuflen);
                        if (rv) {
                                if (rv == YPERR_MAP) {
                                        if (yp_match(st->yp_domain,
                                            "services.byname", buf,
                                            strlen(buf), &resultbuf,
                                            &resultbuflen)) {
                                                rv = NS_NOTFOUND;
                                                goto fin;
                                        }
                                } else {
                                        rv = NS_NOTFOUND;
                                        goto fin;
                                }
                        }

                        break;
                case nss_lt_all:
                        if (!st->yp_stepping) {
                                free(st->yp_key);
                                rv = yp_first(st->yp_domain, "services.byname",
                                    &st->yp_key, &st->yp_keylen, &resultbuf,
                                    &resultbuflen);
                                if (rv) {
                                        rv = NS_NOTFOUND;
                                        goto fin;
                                }
                                st->yp_stepping = 1;
                        } else {
                                lastkey = st->yp_key;
                                rv = yp_next(st->yp_domain, "services.byname",
                                    st->yp_key, st->yp_keylen, &st->yp_key,
                                    &st->yp_keylen, &resultbuf, &resultbuflen);
                                free(lastkey);
                                if (rv) {
                                        st->yp_stepping = 0;
                                        rv = NS_NOTFOUND;
                                        goto fin;
                                }
                        }
                        break;
                }

                rv = parse_result(serv, buffer, bufsize, resultbuf,
                    resultbuflen, errnop);
                free(resultbuf);

        } while (!(rv & NS_TERMINATE) && how == nss_lt_all);

fin:
        free(buf);
        if (rv == NS_SUCCESS && retval != NULL)
                *(struct servent **)retval = serv;

        return (rv);
}

static int
nis_setservent(void *result, void *mdata, va_list ap)
{
        struct nis_state *st;
        int rv;

        rv = nis_getstate(&st);
        if (rv != 0)
                return (NS_UNAVAIL);

        switch ((enum constants)(uintptr_t)mdata) {
        case SETSERVENT:
        case ENDSERVENT:
                free(st->yp_key);
                st->yp_key = NULL;
                st->yp_stepping = 0;
                break;
        default:
                break;
        }

        return (NS_UNAVAIL);
}
#endif

/* compat backend implementation */
static int
compat_setservent(void *retval, void *mdata, va_list ap)
{
        static const ns_src compat_src[] = {
#ifdef YP
                { NSSRC_NIS, NS_SUCCESS },
#endif
                { NULL, 0 }
        };
        ns_dtab compat_dtab[] = {
                { NSSRC_DB, db_setservent, mdata },
#ifdef YP
                { NSSRC_NIS, nis_setservent, mdata },
#endif
                { NULL, NULL, NULL }
        };
        int f;

        (void)files_setservent(retval, mdata, ap);

        switch ((enum constants)(uintptr_t)mdata) {
        case SETSERVENT:
                f = va_arg(ap,int);
                (void)nsdispatch(retval, compat_dtab, NSDB_SERVICES_COMPAT,
                    "setservent", compat_src, f);
                break;
        case ENDSERVENT:
                (void)nsdispatch(retval, compat_dtab, NSDB_SERVICES_COMPAT,
                    "endservent", compat_src);
                break;
        default:
                break;
        }

        return (NS_UNAVAIL);
}

#ifdef NS_CACHING
static int
serv_id_func(char *buffer, size_t *buffer_size, va_list ap, void *cache_mdata)
{
        char *name;
        char *proto;
        int port;

        size_t desired_size, size, size2;
        enum nss_lookup_type lookup_type;
        int res = NS_UNAVAIL;

        lookup_type = (enum nss_lookup_type)(uintptr_t)cache_mdata;
        switch (lookup_type) {
        case nss_lt_name:
                name = va_arg(ap, char *);
                proto = va_arg(ap, char *);

                size = strlen(name);
                desired_size = sizeof(enum nss_lookup_type) + size + 1;
                if (proto != NULL) {
                        size2 = strlen(proto);
                        desired_size += size2 + 1;
                } else
                        size2 = 0;

                if (desired_size > *buffer_size) {
                        res = NS_RETURN;
                        goto fin;
                }

                memcpy(buffer, &lookup_type, sizeof(enum nss_lookup_type));
                memcpy(buffer + sizeof(enum nss_lookup_type), name, size + 1);

                if (proto != NULL)
                        memcpy(buffer + sizeof(enum nss_lookup_type) + size + 1,
                            proto, size2 + 1);

                res = NS_SUCCESS;
                break;
        case nss_lt_id:
                port = va_arg(ap, int);
                proto = va_arg(ap, char *);

                desired_size = sizeof(enum nss_lookup_type) + sizeof(int);
                if (proto != NULL) {
                        size = strlen(proto);
                        desired_size += size + 1;
                } else
                        size = 0;

                if (desired_size > *buffer_size) {
                        res = NS_RETURN;
                        goto fin;
                }

                memcpy(buffer, &lookup_type, sizeof(enum nss_lookup_type));
                memcpy(buffer + sizeof(enum nss_lookup_type), &port,
                    sizeof(int));

                if (proto != NULL)
                        memcpy(buffer + sizeof(enum nss_lookup_type) +
                            sizeof(int), proto, size + 1);

                res = NS_SUCCESS;
                break;
        default:
                /* should be unreachable */
                return (NS_UNAVAIL);
        }

fin:
        *buffer_size = desired_size;
        return (res);
}

static int
serv_marshal_func(char *buffer, size_t *buffer_size, void *retval, va_list ap,
    void *cache_mdata)
{
        char *name __unused;
        char *proto __unused;
        int port __unused;
        struct servent *serv;
        char *orig_buf __unused;
        size_t orig_buf_size __unused;

        struct servent new_serv;
        size_t desired_size;
        char **alias;
        char *p;
        size_t size;
        size_t aliases_size;

        switch ((enum nss_lookup_type)(uintptr_t)cache_mdata) {
        case nss_lt_name:
                name = va_arg(ap, char *);
                proto = va_arg(ap, char *);
                break;
        case nss_lt_id:
                port = va_arg(ap, int);
                proto = va_arg(ap, char *);
                break;
        case nss_lt_all:
                break;
        default:
                /* should be unreachable */
                return (NS_UNAVAIL);
        }

        serv = va_arg(ap, struct servent *);
        orig_buf = va_arg(ap, char *);
        orig_buf_size = va_arg(ap, size_t);

        desired_size = _ALIGNBYTES + sizeof(struct servent) + sizeof(char *);
        if (serv->s_name != NULL)
                desired_size += strlen(serv->s_name) + 1;
        if (serv->s_proto != NULL)
                desired_size += strlen(serv->s_proto) + 1;

        aliases_size = 0;
        if (serv->s_aliases != NULL) {
                for (alias = serv->s_aliases; *alias; ++alias) {
                        desired_size += strlen(*alias) + 1;
                        ++aliases_size;
                }

                desired_size += _ALIGNBYTES +
                    sizeof(char *) * (aliases_size + 1);
        }

        if (*buffer_size < desired_size) {
                /* this assignment is here for future use */
                *buffer_size = desired_size;
                return (NS_RETURN);
        }

        memcpy(&new_serv, serv, sizeof(struct servent));
        memset(buffer, 0, desired_size);

        *buffer_size = desired_size;
        p = buffer + sizeof(struct servent) + sizeof(char *);
        memcpy(buffer + sizeof(struct servent), &p, sizeof(char *));
        p = (char *)_ALIGN(p);

        if (new_serv.s_name != NULL) {
                size = strlen(new_serv.s_name);
                memcpy(p, new_serv.s_name, size);
                new_serv.s_name = p;
                p += size + 1;
        }

        if (new_serv.s_proto != NULL) {
                size = strlen(new_serv.s_proto);
                memcpy(p, new_serv.s_proto, size);
                new_serv.s_proto = p;
                p += size + 1;
        }

        if (new_serv.s_aliases != NULL) {
                p = (char *)_ALIGN(p);
                memcpy(p, new_serv.s_aliases, sizeof(char *) * aliases_size);
                new_serv.s_aliases = (char **)p;
                p += sizeof(char *) * (aliases_size + 1);

                for (alias = new_serv.s_aliases; *alias; ++alias) {
                        size = strlen(*alias);
                        memcpy(p, *alias, size);
                        *alias = p;
                        p += size + 1;
                }
        }

        memcpy(buffer, &new_serv, sizeof(struct servent));
        return (NS_SUCCESS);
}

static int
serv_unmarshal_func(char *buffer, size_t buffer_size, void *retval, va_list ap,
    void *cache_mdata)
{
        char *name __unused;
        char *proto __unused;
        int port __unused;
        struct servent *serv;
        char *orig_buf;
        char *p;
        char **alias;
        size_t orig_buf_size;
        int *ret_errno;

        switch ((enum nss_lookup_type)(uintptr_t)cache_mdata) {
        case nss_lt_name:
                name = va_arg(ap, char *);
                proto = va_arg(ap, char *);
                break;
        case nss_lt_id:
                port = va_arg(ap, int);
                proto = va_arg(ap, char *);
                break;
        case nss_lt_all:
                break;
        default:
                /* should be unreachable */
                return (NS_UNAVAIL);
        }

        serv = va_arg(ap, struct servent *);
        orig_buf = va_arg(ap, char *);
        orig_buf_size = va_arg(ap, size_t);
        ret_errno = va_arg(ap, int *);

        if (orig_buf_size <
            buffer_size - sizeof(struct servent) - sizeof(char *)) {
                *ret_errno = ERANGE;
                return (NS_RETURN);
        }

        memcpy(serv, buffer, sizeof(struct servent));
        memcpy(&p, buffer + sizeof(struct servent), sizeof(char *));

        orig_buf = (char *)_ALIGN(orig_buf);
        memcpy(orig_buf, buffer + sizeof(struct servent) + sizeof(char *) +
            __nss_buf_misalignment(p),
            buffer_size - sizeof(struct servent) - sizeof(char *) -
            __nss_buf_misalignment(p));
        p = (char *)_ALIGN(p);

        NS_APPLY_OFFSET(serv->s_name, orig_buf, p, char *);
        NS_APPLY_OFFSET(serv->s_proto, orig_buf, p, char *);
        if (serv->s_aliases != NULL) {
                NS_APPLY_OFFSET(serv->s_aliases, orig_buf, p, char **);

                for (alias = serv->s_aliases; *alias; ++alias)
                        NS_APPLY_OFFSET(*alias, orig_buf, p, char *);
        }

        if (retval != NULL)
                *((struct servent **)retval) = serv;
        return (NS_SUCCESS);
}

NSS_MP_CACHE_HANDLING(services);
#endif /* NS_CACHING */

/* get**_r functions implementation */
int
getservbyname_r(const char *name, const char *proto, struct servent *serv,
    char *buffer, size_t bufsize, struct servent **result)
{
        static const struct servent_mdata mdata = { nss_lt_name, 0 };
        static const struct servent_mdata compat_mdata = { nss_lt_name, 1 };
#ifdef NS_CACHING
        static const nss_cache_info cache_info =
        NS_COMMON_CACHE_INFO_INITIALIZER(
                services, (void *)nss_lt_name,
                serv_id_func, serv_marshal_func, serv_unmarshal_func);
#endif /* NS_CACHING */
        static const ns_dtab dtab[] = {
                { NSSRC_FILES, files_servent, (void *)&mdata },
                { NSSRC_DB, db_servent, (void *)nss_lt_name },
#ifdef YP
                { NSSRC_NIS, nis_servent, (void *)nss_lt_name },
#endif
                { NSSRC_COMPAT, files_servent, (void *)&compat_mdata },
#ifdef NS_CACHING
                NS_CACHE_CB(&cache_info)
#endif
                { NULL, NULL, NULL }
        };
        int     rv, ret_errno;

        ret_errno = 0;
        *result = NULL;
        rv = nsdispatch(result, dtab, NSDB_SERVICES, "getservbyname_r",
            defaultsrc, name, proto, serv, buffer, bufsize, &ret_errno);

        if (rv == NS_SUCCESS)
                return (0);
        else
                return (ret_errno);
}

int
getservbyport_r(int port, const char *proto, struct servent *serv,
    char *buffer, size_t bufsize, struct servent **result)
{
        static const struct servent_mdata mdata = { nss_lt_id, 0 };
        static const struct servent_mdata compat_mdata = { nss_lt_id, 1 };
#ifdef NS_CACHING
        static const nss_cache_info cache_info =
        NS_COMMON_CACHE_INFO_INITIALIZER(
                services, (void *)nss_lt_id,
                serv_id_func, serv_marshal_func, serv_unmarshal_func);
#endif
        static const ns_dtab dtab[] = {
                { NSSRC_FILES, files_servent, (void *)&mdata },
                { NSSRC_DB, db_servent, (void *)nss_lt_id },
#ifdef YP
                { NSSRC_NIS, nis_servent, (void *)nss_lt_id },
#endif
                { NSSRC_COMPAT, files_servent, (void *)&compat_mdata },
#ifdef NS_CACHING
                NS_CACHE_CB(&cache_info)
#endif
                { NULL, NULL, NULL }
        };
        int rv, ret_errno;

        ret_errno = 0;
        *result = NULL;
        rv = nsdispatch(result, dtab, NSDB_SERVICES, "getservbyport_r",
            defaultsrc, port, proto, serv, buffer, bufsize, &ret_errno);

        if (rv == NS_SUCCESS)
                return (0);
        else
                return (ret_errno);
}

int
getservent_r(struct servent *serv, char *buffer, size_t bufsize,
    struct servent **result)
{
        static const struct servent_mdata mdata = { nss_lt_all, 0 };
        static const struct servent_mdata compat_mdata = { nss_lt_all, 1 };
#ifdef NS_CACHING
        static const nss_cache_info cache_info = NS_MP_CACHE_INFO_INITIALIZER(
                services, (void *)nss_lt_all,
                serv_marshal_func, serv_unmarshal_func);
#endif
        static const ns_dtab dtab[] = {
                { NSSRC_FILES, files_servent, (void *)&mdata },
                { NSSRC_DB, db_servent, (void *)nss_lt_all },
#ifdef YP
                { NSSRC_NIS, nis_servent, (void *)nss_lt_all },
#endif
                { NSSRC_COMPAT, files_servent, (void *)&compat_mdata },
#ifdef NS_CACHING
                NS_CACHE_CB(&cache_info)
#endif
                { NULL, NULL, NULL }
        };
        int rv, ret_errno;

        ret_errno = 0;
        *result = NULL;
        rv = nsdispatch(result, dtab, NSDB_SERVICES, "getservent_r",
            defaultsrc, serv, buffer, bufsize, &ret_errno);

        if (rv == NS_SUCCESS)
                return (0);
        else
                return (ret_errno);
}

void
setservent(int stayopen)
{
#ifdef NS_CACHING
        static const nss_cache_info cache_info = NS_MP_CACHE_INFO_INITIALIZER(
                services, (void *)nss_lt_all,
                NULL, NULL);
#endif
        static const ns_dtab dtab[] = {
                { NSSRC_FILES, files_setservent, (void *)SETSERVENT },
                { NSSRC_DB, db_setservent, (void *)SETSERVENT },
#ifdef YP
                { NSSRC_NIS, nis_setservent, (void *)SETSERVENT },
#endif
                { NSSRC_COMPAT, compat_setservent, (void *)SETSERVENT },
#ifdef NS_CACHING
                NS_CACHE_CB(&cache_info)
#endif
                { NULL, NULL, NULL }
        };

        (void)nsdispatch(NULL, dtab, NSDB_SERVICES, "setservent", defaultsrc,
            stayopen);
}

void
endservent(void)
{
#ifdef NS_CACHING
        static const nss_cache_info cache_info = NS_MP_CACHE_INFO_INITIALIZER(
                services, (void *)nss_lt_all,
                NULL, NULL);
#endif
        static const ns_dtab dtab[] = {
                { NSSRC_FILES, files_setservent, (void *)ENDSERVENT },
                { NSSRC_DB, db_setservent, (void *)ENDSERVENT },
#ifdef YP
                { NSSRC_NIS, nis_setservent, (void *)ENDSERVENT },
#endif
                { NSSRC_COMPAT, compat_setservent, (void *)ENDSERVENT },
#ifdef NS_CACHING
                NS_CACHE_CB(&cache_info)
#endif
                { NULL, NULL, NULL }
        };

        (void)nsdispatch(NULL, dtab, NSDB_SERVICES, "endservent", defaultsrc);
}

/* get** wrappers for get**_r functions implementation */
static void
servent_endstate(void *p)
{
        if (p == NULL)
                return;

        free(((struct servent_state *)p)->buffer);
        free(p);
}

static int
wrap_getservbyname_r(struct key key, struct servent *serv, char *buffer,
    size_t bufsize, struct servent **res)
{
        return (getservbyname_r(key.name, key.proto, serv, buffer, bufsize,
            res));
}

static int
wrap_getservbyport_r(struct key key, struct servent *serv, char *buffer,
    size_t bufsize, struct servent **res)
{
        return (getservbyport_r(key.port, key.proto, serv, buffer, bufsize,
            res));
}

static  int
wrap_getservent_r(struct key key, struct servent *serv, char *buffer,
    size_t bufsize, struct servent **res)
{
        return (getservent_r(serv, buffer, bufsize, res));
}

static struct servent *
getserv(int (*fn)(struct key, struct servent *, char *, size_t,
    struct servent **), struct key key)
{
        int rv;
        struct servent *res;
        struct servent_state * st;

        rv = servent_getstate(&st);
        if (rv != 0) {
                errno = rv;
                return NULL;
        }

        if (st->buffer == NULL) {
                st->buffer = malloc(SERVENT_STORAGE_INITIAL);
                if (st->buffer == NULL)
                        return (NULL);
                st->bufsize = SERVENT_STORAGE_INITIAL;
        }
        do {
                rv = fn(key, &st->serv, st->buffer, st->bufsize, &res);
                if (res == NULL && rv == ERANGE) {
                        free(st->buffer);
                        if ((st->bufsize << 1) > SERVENT_STORAGE_MAX) {
                                st->buffer = NULL;
                                errno = ERANGE;
                                return (NULL);
                        }
                        st->bufsize <<= 1;
                        st->buffer = malloc(st->bufsize);
                        if (st->buffer == NULL)
                                return (NULL);
                }
        } while (res == NULL && rv == ERANGE);
        if (rv != 0)
                errno = rv;

        return (res);
}

struct servent *
getservbyname(const char *name, const char *proto)
{
        struct key key;

        key.name = name;
        key.proto = proto;

        return (getserv(wrap_getservbyname_r, key));
}

struct servent *
getservbyport(int port, const char *proto)
{
        struct key key;

        key.port = port;
        key.proto = proto;

        return (getserv(wrap_getservbyport_r, key));
}

struct servent *
getservent(void)
{
        struct key key;

        key.proto = NULL;
        key.port = 0;

        return (getserv(wrap_getservent_r, key));
}