/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define NB_NEEDRESOLVER #include #include #include #include #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); }