root/usr/src/lib/libsmbfs/smb/nbns_rq.c
/*
 * Copyright (c) 2000, Boris Popov
 * 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. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *    This product includes software developed by Boris Popov.
 * 4. Neither the name of the author nor the names of any co-contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
 *
 * $Id: nbns_rq.c,v 1.9 2005/02/24 02:04:38 lindak Exp $
 */

/*
 * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
 */

#include <sys/param.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <ctype.h>
#include <netdb.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <stdio.h>
#include <unistd.h>
#include <libintl.h>

#include <netinet/in.h>
#include <arpa/inet.h>
#include <tsol/label.h>

#define NB_NEEDRESOLVER
#include <netsmb/netbios.h>
#include <netsmb/smb_lib.h>
#include <netsmb/nb_lib.h>
#include <netsmb/mchain.h>

#include "charsets.h"
#include "private.h"

/*
 * nbns request
 */
struct nbns_rq {
        int             nr_opcode;
        int             nr_nmflags;
        int             nr_rcode;
        int             nr_qdcount;
        int             nr_ancount;
        int             nr_nscount;
        int             nr_arcount;
        struct nb_name  *nr_qdname;
        uint16_t        nr_qdtype;
        uint16_t        nr_qdclass;
        struct in_addr  nr_dest;        /* receiver of query */
        struct sockaddr_in nr_sender;   /* sender of response */
        int             nr_rpnmflags;
        int             nr_rprcode;
        uint16_t        nr_rpancount;
        uint16_t        nr_rpnscount;
        uint16_t        nr_rparcount;
        uint16_t        nr_trnid;
        struct nb_ctx   *nr_nbd;
        struct mbdata   nr_rq;
        struct mbdata   nr_rp;
        struct nb_ifdesc *nr_if;
        int             nr_flags;
        int             nr_fd;
        int             nr_maxretry;
};
typedef struct nbns_rq nbns_rq_t;

static int  nbns_rq_create(int opcode, struct nb_ctx *ctx,
    struct nbns_rq **rqpp);
static void nbns_rq_done(struct nbns_rq *rqp);
static int  nbns_rq_getrr(struct nbns_rq *rqp, struct nbns_rr *rrp);
static int  nbns_rq_prepare(struct nbns_rq *rqp);
static int  nbns_rq(struct nbns_rq *rqp);

/*
 * Call NetBIOS name lookup and return a result in the
 * same form as getaddrinfo(3) returns.  Return code is
 * zero or one of the EAI_xxx codes like getaddrinfo.
 */
int
nbns_getaddrinfo(const char *name, struct nb_ctx *nbc, struct addrinfo **res)
{
        struct addrinfo *nai = NULL;
        struct sockaddr *sap = NULL;
        char *ucname = NULL;
        int err;

        /*
         * Try NetBIOS name lookup.
         */
        if (strlen(name) >= NB_NAMELEN) {
                err = EAI_OVERFLOW;
                goto out;
        }
        ucname = utf8_str_toupper(name);
        if (ucname == NULL)
                goto nomem;

        /* Note: this returns an NBERROR value. */
        err = nbns_resolvename(ucname, nbc, &sap);
        if (err) {
                if (smb_verbose)
                        smb_error(dgettext(TEXT_DOMAIN,
                            "nbns_resolvename: %s"),
                            err, name);
                err = EAI_NODATA;
                goto out;
        }
        /* Note: sap allocated */

        /*
         * Build the addrinfo struct to return.
         */
        nai = malloc(sizeof (*nai));
        if (nai == NULL)
                goto nomem;
        bzero(nai, sizeof (*nai));

        nai->ai_flags = AI_CANONNAME;
        nai->ai_family = sap->sa_family;
        nai->ai_socktype = SOCK_STREAM;
        nai->ai_canonname = ucname;
        ucname = NULL;

        /*
         * The type of this is really sockaddr_in,
         * but is returned in the generic form.
         * See nbns_resolvename.
         */
        nai->ai_addrlen = sizeof (struct sockaddr_in);
        nai->ai_addr = sap;

        *res = nai;
        return (0);

nomem:
        err = EAI_MEMORY;
out:
        if (nai != NULL)
                free(nai);
        if (sap)
                free(sap);
        if (ucname)
                free(ucname);
        *res = NULL;

        return (err);
}

int
nbns_resolvename(const char *name, struct nb_ctx *ctx, struct sockaddr **adpp)
{
        struct nbns_rq *rqp;
        struct nb_name nn;
        struct nbns_rr rr;
        struct sockaddr_in *dest;
        int error, rdrcount, len;

        if (strlen(name) >= NB_NAMELEN)
                return (NBERROR(NBERR_NAMETOOLONG));
        error = nbns_rq_create(NBNS_OPCODE_QUERY, ctx, &rqp);
        if (error)
                return (error);
        /*
         * Pad the name with blanks, but
         * leave the "type" byte NULL.
         * nb_name_encode adds the type.
         */
        bzero(&nn, sizeof (nn));
        snprintf(nn.nn_name, NB_NAMELEN, "%-15.15s", name);
        nn.nn_type = NBT_SERVER;
        nn.nn_scope = ctx->nb_scope;
        rqp->nr_nmflags = NBNS_NMFLAG_RD;
        rqp->nr_qdname = &nn;
        rqp->nr_qdtype = NBNS_QUESTION_TYPE_NB;
        rqp->nr_qdclass = NBNS_QUESTION_CLASS_IN;
        rqp->nr_qdcount = 1;
        rqp->nr_maxretry = 5;

        error = nbns_rq_prepare(rqp);
        if (error) {
                nbns_rq_done(rqp);
                return (error);
        }
        rdrcount = NBNS_MAXREDIRECTS;
        for (;;) {
                error = nbns_rq(rqp);
                if (error)
                        break;
                if ((rqp->nr_rpnmflags & NBNS_NMFLAG_AA) == 0) {
                        /*
                         * Not an authoritative answer.  Query again
                         * using the NS address in the 2nd record.
                         */
                        if (rdrcount-- == 0) {
                                error = NBERROR(NBERR_TOOMANYREDIRECTS);
                                break;
                        }
                        error = nbns_rq_getrr(rqp, &rr);
                        if (error)
                                break;
                        error = nbns_rq_getrr(rqp, &rr);
                        if (error)
                                break;
                        bcopy(rr.rr_data, &rqp->nr_dest, 4);
                        continue;
                }
                if (rqp->nr_rpancount == 0) {
                        error = NBERROR(NBERR_HOSTNOTFOUND);
                        break;
                }
                error = nbns_rq_getrr(rqp, &rr);
                if (error)
                        break;
                len = sizeof (struct sockaddr_in);
                dest = malloc(len);
                if (dest == NULL)
                        return (ENOMEM);
                bzero(dest, len);
                /*
                 * Solaris sockaddr_in doesn't a sin_len field.
                 * dest->sin_len = len;
                 */
                dest->sin_family = AF_NETBIOS;  /* nb_lib.h */
                bcopy(rr.rr_data + 2, &dest->sin_addr.s_addr, 4);
                *adpp = (struct sockaddr *)dest;
                ctx->nb_lastns = rqp->nr_sender;
                break;
        }
        nbns_rq_done(rqp);
        return (error);
}

/*
 * NB: system, workgroup are both NB_NAMELEN
 */
int
nbns_getnodestatus(struct nb_ctx *ctx,
    struct in_addr *targethost, char *system, char *workgroup)
{
        struct nbns_rq *rqp;
        struct nbns_rr rr;
        struct nb_name nn;
        struct nbns_nr *nrp;
        char nrtype;
        char *cp, *retname = NULL;
        unsigned char nrcount;
        int error, i, foundserver = 0, foundgroup = 0;

        error = nbns_rq_create(NBNS_OPCODE_QUERY, ctx, &rqp);
        if (error)
                return (error);
        bzero(&nn, sizeof (nn));
        strcpy((char *)nn.nn_name, "*");
        nn.nn_scope = ctx->nb_scope;
        nn.nn_type = NBT_WKSTA;
        rqp->nr_nmflags = 0;
        rqp->nr_qdname = &nn;
        rqp->nr_qdtype = NBNS_QUESTION_TYPE_NBSTAT;
        rqp->nr_qdclass = NBNS_QUESTION_CLASS_IN;
        rqp->nr_qdcount = 1;
        rqp->nr_maxretry = 2;

        rqp->nr_dest = *targethost;
        error = nbns_rq_prepare(rqp);
        if (error) {
                nbns_rq_done(rqp);
                return (error);
        }

        /*
         * Darwin had a loop here, allowing redirect, etc.
         * but we only handle point-to-point for node status.
         */
        error = nbns_rq(rqp);
        if (error)
                goto out;
        if (rqp->nr_rpancount == 0) {
                error = NBERROR(NBERR_HOSTNOTFOUND);
                goto out;
        }
        error = nbns_rq_getrr(rqp, &rr);
        if (error)
                goto out;

        /* Compiler didn't like cast on lvalue++ */
        nrcount = *((unsigned char *)rr.rr_data);
        rr.rr_data++;
        /* LINTED */
        for (i = 1, nrp = (struct nbns_nr *)rr.rr_data;
            i <= nrcount; ++i, ++nrp) {
                nrtype = nrp->ns_name[NB_NAMELEN-1];
                /* Terminate the string: */
                nrp->ns_name[NB_NAMELEN-1] = (char)0;
                /* Strip off trailing spaces */
                for (cp = &nrp->ns_name[NB_NAMELEN-2];
                    cp >= nrp->ns_name; --cp) {
                        if (*cp != (char)0x20)
                                break;
                        *cp = (char)0;
                }
                nrp->ns_flags = ntohs(nrp->ns_flags);
                DPRINT(" %s[%02x] Flags 0x%x",
                    nrp->ns_name, nrtype, nrp->ns_flags);
                if (nrp->ns_flags & NBNS_GROUPFLG) {
                        if (!foundgroup ||
                            (foundgroup != NBT_WKSTA+1 &&
                            nrtype == NBT_WKSTA)) {
                                strlcpy(workgroup, nrp->ns_name,
                                    NB_NAMELEN);
                                foundgroup = nrtype+1;
                        }
                } else {
                        /*
                         * Track at least ONE name, in case
                         * no server name is found
                         */
                        retname = nrp->ns_name;
                }
                /*
                 * Keep the first NBT_SERVER name.
                 */
                if (nrtype == NBT_SERVER && foundserver == 0) {
                        strlcpy(system, nrp->ns_name,
                            NB_NAMELEN);
                        foundserver = 1;
                }
        }
        if (foundserver == 0 && retname != NULL)
                strlcpy(system, retname, NB_NAMELEN);
        ctx->nb_lastns = rqp->nr_sender;

out:
        nbns_rq_done(rqp);
        return (error);
}

int
nbns_rq_create(int opcode, struct nb_ctx *ctx, struct nbns_rq **rqpp)
{
        struct nbns_rq *rqp;
        static uint16_t trnid;
        int error;

        if (trnid == 0)
                trnid = getpid();
        rqp = malloc(sizeof (*rqp));
        if (rqp == NULL)
                return (ENOMEM);
        bzero(rqp, sizeof (*rqp));
        error = mb_init_sz(&rqp->nr_rq, NBDG_MAXSIZE);
        if (error) {
                free(rqp);
                return (error);
        }
        rqp->nr_opcode = opcode;
        rqp->nr_nbd = ctx;
        rqp->nr_trnid = trnid++;
        *rqpp = rqp;
        return (0);
}

void
nbns_rq_done(struct nbns_rq *rqp)
{
        if (rqp == NULL)
                return;
        if (rqp->nr_fd >= 0)
                close(rqp->nr_fd);
        mb_done(&rqp->nr_rq);
        mb_done(&rqp->nr_rp);
        if (rqp->nr_if)
                free(rqp->nr_if);
        free(rqp);
}

/*
 * Extract resource record from the packet. Assume that there is only
 * one mbuf.
 */
int
nbns_rq_getrr(struct nbns_rq *rqp, struct nbns_rr *rrp)
{
        struct mbdata *mbp = &rqp->nr_rp;
        uchar_t *cp;
        int error, len;

        bzero(rrp, sizeof (*rrp));
        cp = (uchar_t *)mbp->mb_pos;
        len = nb_encname_len(cp);
        if (len < 1)
                return (NBERROR(NBERR_INVALIDRESPONSE));
        rrp->rr_name = cp;
        error = md_get_mem(mbp, NULL, len, MB_MSYSTEM);
        if (error)
                return (error);
        md_get_uint16be(mbp, &rrp->rr_type);
        md_get_uint16be(mbp, &rrp->rr_class);
        md_get_uint32be(mbp, &rrp->rr_ttl);
        md_get_uint16be(mbp, &rrp->rr_rdlength);
        rrp->rr_data = (uchar_t *)mbp->mb_pos;
        error = md_get_mem(mbp, NULL, rrp->rr_rdlength, MB_MSYSTEM);
        return (error);
}

int
nbns_rq_prepare(struct nbns_rq *rqp)
{
        struct nb_ctx *ctx = rqp->nr_nbd;
        struct mbdata *mbp = &rqp->nr_rq;
        uint16_t ofr; /* opcode, flags, rcode */
        int error;

        error = mb_init_sz(&rqp->nr_rp, NBDG_MAXSIZE);
        if (error)
                return (error);

        /*
         * When looked into the ethereal trace, 'nmblookup' command sets this
         * flag. We will also set.
         */
        mb_put_uint16be(mbp, rqp->nr_trnid);
        ofr = ((rqp->nr_opcode & 0x1F) << 11) |
            ((rqp->nr_nmflags & 0x7F) << 4); /* rcode=0 */
        mb_put_uint16be(mbp, ofr);
        mb_put_uint16be(mbp, rqp->nr_qdcount);
        mb_put_uint16be(mbp, rqp->nr_ancount);
        mb_put_uint16be(mbp, rqp->nr_nscount);
        error = mb_put_uint16be(mbp, rqp->nr_arcount);
        if (rqp->nr_qdcount) {
                if (rqp->nr_qdcount > 1)
                        return (EINVAL);
                (void) nb_name_encode(mbp, rqp->nr_qdname);
                mb_put_uint16be(mbp, rqp->nr_qdtype);
                error = mb_put_uint16be(mbp, rqp->nr_qdclass);
        }
        if (error)
                return (error);
        error = m_lineup(mbp->mb_top, &mbp->mb_top);
        if (error)
                return (error);
        if (ctx->nb_timo == 0)
                ctx->nb_timo = 1;       /* by default 1 second */
        return (0);
}

static int
nbns_rq_recv(struct nbns_rq *rqp)
{
        struct mbdata *mbp = &rqp->nr_rp;
        void *rpdata = mtod(mbp->mb_top, void *);
        fd_set rd, wr, ex;
        struct timeval tv;
        struct sockaddr_in sender;
        int s = rqp->nr_fd;
        int n, len;

        FD_ZERO(&rd);
        FD_ZERO(&wr);
        FD_ZERO(&ex);
        FD_SET(s, &rd);

        tv.tv_sec = rqp->nr_nbd->nb_timo;
        tv.tv_usec = 0;

        n = select(s + 1, &rd, &wr, &ex, &tv);
        if (n == -1)
                return (-1);
        if (n == 0)
                return (ETIMEDOUT);
        if (FD_ISSET(s, &rd) == 0)
                return (ETIMEDOUT);
        len = sizeof (sender);
        n = recvfrom(s, rpdata, mbp->mb_top->m_maxlen, 0,
            (struct sockaddr *)&sender, &len);
        if (n < 0)
                return (errno);
        mbp->mb_top->m_len = mbp->mb_count = n;
        rqp->nr_sender = sender;
        return (0);
}

static int
nbns_rq_opensocket(struct nbns_rq *rqp)
{
        struct sockaddr_in locaddr;
        int opt = 1, s;
        struct nb_ctx *ctx = rqp->nr_nbd;

        s = rqp->nr_fd = socket(AF_INET, SOCK_DGRAM, 0);
        if (s < 0)
                return (errno);
        if (ctx->nb_flags & NBCF_BC_ENABLE) {
                if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &opt,
                    sizeof (opt)) < 0)
                        return (errno);
        }
        if (is_system_labeled())
                (void) setsockopt(s, SOL_SOCKET, SO_MAC_EXEMPT, &opt,
                    sizeof (opt));
        bzero(&locaddr, sizeof (locaddr));
        locaddr.sin_family = AF_INET;
        /* locaddr.sin_len = sizeof (locaddr); */
        if (bind(s, (struct sockaddr *)&locaddr, sizeof (locaddr)) < 0)
                return (errno);
        return (0);
}

static int
nbns_rq_send(struct nbns_rq *rqp, in_addr_t ina)
{
        struct sockaddr_in dest;
        struct mbdata *mbp = &rqp->nr_rq;
        int s = rqp->nr_fd;
        uint16_t ofr, ofr_save; /* opcode, nmflags, rcode */
        uint16_t *datap;
        uint8_t nmflags;
        int rc;

        bzero(&dest, sizeof (dest));
        dest.sin_family = AF_INET;
        dest.sin_port = htons(IPPORT_NETBIOS_NS);
        dest.sin_addr.s_addr = ina;

        if (ina == INADDR_BROADCAST) {
                /* Turn on the broadcast bit. */
                nmflags = rqp->nr_nmflags | NBNS_NMFLAG_BCAST;
                /*LINTED*/
                datap = mtod(mbp->mb_top, uint16_t *);
                ofr = ((rqp->nr_opcode & 0x1F) << 11) |
                    ((nmflags & 0x7F) << 4); /* rcode=0 */
                ofr_save = datap[1];
                datap[1] = htons(ofr);
        }

        rc = sendto(s, mtod(mbp->mb_top, char *), mbp->mb_count, 0,
            (struct sockaddr *)&dest, sizeof (dest));

        if (ina == INADDR_BROADCAST) {
                /* Turn the broadcast bit back off. */
                datap[1] = ofr_save;
        }


        if (rc < 0)
                return (errno);

        return (0);
}

int
nbns_rq(struct nbns_rq *rqp)
{
        struct nb_ctx *ctx = rqp->nr_nbd;
        struct mbdata *mbp = &rqp->nr_rq;
        uint16_t ofr, rpid;
        int error, tries, maxretry;

        error = nbns_rq_opensocket(rqp);
        if (error)
                return (error);

        maxretry = rqp->nr_maxretry;
        for (tries = 0; tries < maxretry; tries++) {

                /*
                 * Minor hack: If nr_dest is set, send there only.
                 * Used by _getnodestatus, _resolvname redirects.
                 */
                if (rqp->nr_dest.s_addr) {
                        error = nbns_rq_send(rqp, rqp->nr_dest.s_addr);
                        if (error) {
                                smb_error(dgettext(TEXT_DOMAIN,
                                    "nbns error %d sending to %s"),
                                    0, error, inet_ntoa(rqp->nr_dest));
                        }
                        goto do_recv;
                }

                if (ctx->nb_wins1) {
                        error = nbns_rq_send(rqp, ctx->nb_wins1);
                        if (error) {
                                smb_error(dgettext(TEXT_DOMAIN,
                                    "nbns error %d sending to wins1"),
                                    0, error);
                        }
                }

                if (ctx->nb_wins2 && (tries > 0)) {
                        error = nbns_rq_send(rqp, ctx->nb_wins2);
                        if (error) {
                                smb_error(dgettext(TEXT_DOMAIN,
                                    "nbns error %d sending to wins2"),
                                    0, error);
                        }
                }

                /*
                 * If broadcast is enabled, start broadcasting
                 * only after wins servers fail to respond, or
                 * immediately if no WINS servers configured.
                 */
                if ((ctx->nb_flags & NBCF_BC_ENABLE) &&
                    ((tries > 1) || (ctx->nb_wins1 == 0))) {
                        error = nbns_rq_send(rqp, INADDR_BROADCAST);
                        if (error) {
                                smb_error(dgettext(TEXT_DOMAIN,
                                    "nbns error %d sending broadcast"),
                                    0, error);
                        }
                }

                /*
                 * Wait for responses from ANY of the above.
                 */
do_recv:
                error = nbns_rq_recv(rqp);
                if (error == ETIMEDOUT)
                        continue;
                if (error) {
                        smb_error(dgettext(TEXT_DOMAIN,
                            "nbns recv error %d"),
                            0, error);
                        return (error);
                }

                mbp = &rqp->nr_rp;
                if (mbp->mb_count < 12)
                        return (NBERROR(NBERR_INVALIDRESPONSE));
                md_get_uint16be(mbp, &rpid);
                if (rpid != rqp->nr_trnid)
                        return (NBERROR(NBERR_INVALIDRESPONSE));
                break;
        }
        if (tries == maxretry)
                return (NBERROR(NBERR_HOSTNOTFOUND));

        md_get_uint16be(mbp, &ofr);
        rqp->nr_rpnmflags = (ofr >> 4) & 0x7F;
        rqp->nr_rprcode = ofr & 0xf;
        if (rqp->nr_rprcode)
                return (NBERROR(rqp->nr_rprcode));
        md_get_uint16be(mbp, &rpid);    /* QDCOUNT */
        md_get_uint16be(mbp, &rqp->nr_rpancount);
        md_get_uint16be(mbp, &rqp->nr_rpnscount);
        md_get_uint16be(mbp, &rqp->nr_rparcount);
        return (0);
}