/* * Copyright (c) 2000, Boris Popov * All rights reserved. * * Portions Copyright (C) 2001 - 2010 Apple Inc. 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.13.140.1 2006/04/14 23:49:37 gcolley Exp $ */ #include #include #include #include #include #include #include "preference.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 sockaddr_in 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 mbchain nr_rq; struct mdchain nr_rp; struct nb_ifdesc *nr_if; int nr_flags; /* endian-ness depends on host */ int nr_fd; int32_t nr_timo; }; static struct nb_ifdesc *nb_iflist; static char * smb_optstrncpy(char *d, char *s, unsigned maxlen) { if (d && s) { strncpy(d, s, maxlen); d[maxlen] = (char)0; } return (d); } static int nbns_rq_create(int opcode, struct smb_prefs *prefs, struct nbns_rq **rqpp) { struct nbns_rq *rqp; static uint16_t trnid; int error; rqp = malloc(sizeof(*rqp)); if (rqp == NULL) return ENOMEM; bzero(rqp, sizeof(*rqp)); error = mb_init(&rqp->nr_rq); if (error) { free(rqp); return error; } if (prefs) { rqp->nr_timo = prefs->NetBIOSResolverTimeout; } else { rqp->nr_timo = DefaultNetBIOSResolverTimeout; } rqp->nr_opcode = opcode; rqp->nr_trnid = trnid++; *rqpp = rqp; return 0; } static 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); md_done(&rqp->nr_rp); free(rqp); } /* * Extract resource record from the packet. Assume that there is only * one mbuf. */ static int nbns_rq_getrr(struct nbns_rq *rqp, struct nbns_rr *rrp) { mdchain_t mdp = &rqp->nr_rp; u_char *cp; int error, len; bzero(rrp, sizeof(*rrp)); cp = (u_char *)(mdp->md_pos); len = nb_encname_len((char *)cp); if (len < 1) return EINVAL; rrp->rr_name = cp; error = md_get_mem(mdp, NULL, len, MB_MSYSTEM); if (error) return error; md_get_uint16be(mdp, &rrp->rr_type); md_get_uint16be(mdp, &rrp->rr_class); md_get_uint32be(mdp, &rrp->rr_ttl); md_get_uint16be(mdp, &rrp->rr_rdlength); rrp->rr_data = (u_char *)mdp->md_pos; error = md_get_mem(mdp, NULL, rrp->rr_rdlength, MB_MSYSTEM); return error; } static int nbns_rq_recv(struct nbns_rq *rqp) { mdchain_t mdp = &rqp->nr_rp; void *rpdata = mbuf_data(mdp->md_top); fd_set rd, wr, ex; struct timeval tv; struct sockaddr_in sender; int s = rqp->nr_fd; int n; socklen_t len; FD_ZERO(&rd); FD_ZERO(&wr); FD_ZERO(&ex); FD_SET(s, &rd); tv.tv_sec = 0; tv.tv_usec = 500000; /* We wait half a second for a response */ 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 = (socklen_t)sizeof(sender); n = (int)recvfrom(s, rpdata, mbuf_maxlen(mdp->md_top), 0, (struct sockaddr*)&sender, &len); if (n < 0) return errno; mbuf_setlen(mdp->md_top, n); rqp->nr_sender = sender; return 0; } static int nbns_rq_opensocket(struct nbns_rq *rqp) { struct sockaddr_in locaddr; int opt, s; s = rqp->nr_fd = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) return errno; if (rqp->nr_flags & NBRQF_BROADCAST) { opt = 1; if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &opt, (socklen_t)sizeof(opt)) < 0) return errno; if (rqp->nr_if == NULL) return ENETDOWN; bzero(&locaddr, sizeof(locaddr)); locaddr.sin_family = AF_INET; locaddr.sin_len = sizeof(locaddr); locaddr.sin_addr = rqp->nr_if->id_addr; rqp->nr_dest.sin_addr.s_addr = rqp->nr_if->id_addr.s_addr | ~rqp->nr_if->id_mask.s_addr; if (bind(s, (struct sockaddr*)&locaddr, (socklen_t)sizeof(locaddr)) < 0) return errno; } return 0; } static int nbns_rq_send(struct nbns_rq *rqp) { mbchain_t mbp = &rqp->nr_rq; int s = rqp->nr_fd; if (sendto(s, mbuf_data(mbp->mb_top), mbp->mb_count, 0, (struct sockaddr*)&rqp->nr_dest, (socklen_t)sizeof(rqp->nr_dest)) < 0) return errno; return 0; } static int nbns_rq_run(struct nbns_rq *rqp, uint16_t *cancel) { mdchain_t mdp; uint16_t rpid; uint8_t nmflags; int error, retrycount; rqp->nr_if = nb_iflist; again: error = nbns_rq_opensocket(rqp); if (error) return error; /* * The configuration file alwows the user to set the total amount of time * we will wait for a NetBIOS name lookup to complete. So will always wait * half a second per attempt. So NetBIOSResolverTimeout is the number of seconds we want to * wait. So NetBIOSResolverTimeout * 2 is the number of retries we will attmept. */ retrycount = rqp->nr_timo * 2; for (;;) { if (cancel && *cancel) return ECANCELED; error = nbns_rq_send(rqp); if (error) return error; error = nbns_rq_recv(rqp); if (error) { if (error != ETIMEDOUT || --retrycount == 0) { if ((rqp->nr_nmflags & NBNS_NMFLAG_BCAST) && rqp->nr_if != NULL && rqp->nr_if->id_next != NULL) { rqp->nr_if = rqp->nr_if->id_next; close(rqp->nr_fd); goto again; } else return error; } continue; } mdp = &rqp->nr_rp; if (md_get_uint16be(mdp, &rpid)) return EINVAL; if (rpid != rqp->nr_trnid) return EINVAL; break; } if (md_get_uint8(mdp, &nmflags)) return EINVAL; rqp->nr_rpnmflags = (nmflags & 7) << 4; if (md_get_uint8(mdp, &nmflags)) return EINVAL; rqp->nr_rpnmflags |= (nmflags & 0xf0) >> 4; rqp->nr_rprcode = nmflags & 0xf; if (rqp->nr_rprcode) return nb_error_to_errno(rqp->nr_rprcode); if (md_get_uint16be(mdp, &rpid)) /* QDCOUNT */ return EINVAL; if (md_get_uint16be(mdp, &rqp->nr_rpancount)) return EINVAL; if (md_get_uint16be(mdp, &rqp->nr_rpnscount)) return EINVAL; if (md_get_uint16be(mdp, &rqp->nr_rparcount)) return EINVAL; return 0; } static int nbns_rq_prepare(struct nbns_rq *rqp) { mbchain_t mbp = &rqp->nr_rq; uint8_t nmflags; u_char *cp; int len, error; error = md_init_rcvsize(&rqp->nr_rp, NBDG_MAXSIZE); if (error) return error; if (rqp->nr_dest.sin_addr.s_addr == htonl(INADDR_BROADCAST)) { rqp->nr_nmflags |= NBNS_NMFLAG_BCAST; rqp->nr_flags |= NBRQF_BROADCAST; if (nb_iflist == NULL) { error = nb_enum_if(&nb_iflist, 100); if (error) return error; } } mb_put_uint16be(mbp, rqp->nr_trnid); nmflags = ((rqp->nr_opcode & 0x1F) << 3) | ((rqp->nr_nmflags & 0x70) >> 4); mb_put_uint8(mbp, nmflags); mb_put_uint8(mbp, (rqp->nr_nmflags & 0x0f) << 4 /* rcode */); mb_put_uint16be(mbp, rqp->nr_qdcount); mb_put_uint16be(mbp, rqp->nr_ancount); mb_put_uint16be(mbp, rqp->nr_nscount); mb_put_uint16be(mbp, rqp->nr_arcount); if (rqp->nr_qdcount) { if (rqp->nr_qdcount > 1) return EINVAL; len = NB_ENCNAMELEN + 2; cp = (u_char *)mb_reserve(mbp, len); if (cp == NULL) return ENOMEM; nb_name_encode(rqp->nr_qdname, cp); mb_put_uint16be(mbp, rqp->nr_qdtype); mb_put_uint16be(mbp, rqp->nr_qdclass); } mb_pullup(mbp); return 0; } /* * Resolve a NetBIOS name to an set of address. */ static int nbns_resolvename_internal(struct nb_ctx *ctx, struct smb_prefs *prefs, const char *name, uint8_t nodeType, CFMutableArrayRef *outAddressArray, uint16_t port, int allowLocalConn, int tryBothPorts, uint16_t *cancel) { CFMutableArrayRef addressArray = NULL; CFMutableDataRef addressData; struct connectAddress conn; struct nbns_rq *rqp; struct nb_name nn; struct nbns_rr rr; int error, rdrcount; u_char *current_ip, *end_of_rr; /* If we are trying both ports always put port 139 in after port 445 */ if (tryBothPorts && (port == NBSS_TCP_PORT_139)) port = SMB_TCP_PORT_445; if (strlen(name) > NB_NAMELEN) return ENAMETOOLONG; addressArray = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks); if (!addressArray) { return ENOMEM; } error = nbns_rq_create(NBNS_OPCODE_QUERY, prefs, &rqp); if (error) { CFRelease(addressArray); return error; } bzero(&nn, sizeof(nn)); strlcpy((char *)nn.nn_name, name, sizeof(nn.nn_name)); nn.nn_type = nodeType; 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; memcpy(&rqp->nr_dest, &ctx->nb_ns, sizeof(rqp->nr_dest)); error = nbns_rq_prepare(rqp); if (error) { goto done; } rdrcount = NBNS_MAXREDIRECTS; for (;;) { error = nbns_rq_run(rqp, cancel); if (error) break; if ((rqp->nr_rpnmflags & NBNS_NMFLAG_AA) == 0) { if (rdrcount-- == 0) { error = ETOOMANYREFS; break; } error = nbns_rq_getrr(rqp, &rr); if (! error) error = nbns_rq_getrr(rqp, &rr); if (error) break; bcopy(rr.rr_data, &rqp->nr_dest.sin_addr, 4); rqp->nr_flags &= ~NBRQF_BROADCAST; continue; } if (rqp->nr_rpancount == 0) { error = EHOSTUNREACH; break; } error = nbns_rq_getrr(rqp, &rr); if (error) break; /* We have an answer, so store away the address of the server that responded */ ctx->nb_sender = rqp->nr_sender; end_of_rr = rr.rr_data + rr.rr_rdlength; for(current_ip = rr.rr_data + 2; current_ip < end_of_rr; current_ip += 6) { bzero(&conn, sizeof(conn)); conn.in4.sin_len = (int)sizeof(struct sockaddr_in); conn.in4.sin_family = AF_INET; conn.in4.sin_port = htons(port); memcpy(&conn.in4.sin_addr, current_ip, 4); /* Check to make sure we are not connecting to ourself */ if (isLocalIPAddress((struct sockaddr *)&conn.addr, port, allowLocalConn)) { smb_log_info("The address for `%s' is a loopback address, not allowed!", ASL_LEVEL_DEBUG, name); error = ELOOP; /* AFP returns ELOOP, so we will do the same */ goto done; } addressData = CFDataCreateMutable(NULL, 0); if (addressData) { /* The name is the netbios name, we need a netbios sockaddr */ if (port == NBSS_TCP_PORT_139) convertToNetBIOSaddr(&conn.storage, name); CFDataAppendBytes(addressData, (const UInt8 *)&conn, (CFIndex)sizeof(conn)); CFArrayAppendValue(addressArray, addressData); CFRelease(addressData); } /* We only try both ports with IPv4 */ if (tryBothPorts) { conn.in4.sin_port = htons(NBSS_TCP_PORT_139); /* The name is the netbios name, we need a netbios sockaddr */ convertToNetBIOSaddr(&conn.storage, name); addressData = CFDataCreateMutable(NULL, 0); if (addressData) { CFDataAppendBytes(addressData, (const UInt8 *)&conn, (CFIndex)sizeof(conn)); CFArrayAppendValue(addressArray, addressData); CFRelease(addressData); } } } break; } /* end big for loop */ done: if (CFArrayGetCount(addressArray) == 0) { error = EHOSTUNREACH; } nbns_rq_done(rqp); if (error) { if (addressArray) CFRelease(addressArray); addressArray = NULL; } *outAddressArray = addressArray; return error; } /* * Resolve a NetBIOS name to an set of address, We always try WINS first if a * server is provide. If no WINS server or we fail to find one with the WINS * server then try broadcast. */ int nbns_resolvename(struct nb_ctx *ctx, struct smb_prefs *prefs, const char *name, uint8_t nodeType, CFMutableArrayRef *outAddressArray, uint16_t port, int allowLocalConn, int tryBothPorts, uint16_t *cancel) { int error; error = nb_ctx_resolve(ctx, prefs->WINSAddresses); if (!error) error = nbns_resolvename_internal(ctx, prefs, name, nodeType, outAddressArray, port, allowLocalConn, tryBothPorts, cancel); /* We tried it with WINS and failed try broadcast now */ if (error && (prefs->WINSAddresses != NULL)) { error = nb_ctx_resolve(ctx, NULL); if (error == 0) { error = nbns_resolvename_internal(ctx, prefs, name, nodeType, outAddressArray, port, allowLocalConn, tryBothPorts, cancel); } } return error; } int nbns_getnodestatus(struct sockaddr *targethost, struct nb_ctx *ctx, struct smb_prefs *prefs, uint16_t *cancel, char *nbt_server, char *workgroup, CFMutableArrayRef nbrrArray) { 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; if (targethost->sa_family != AF_INET) return EINVAL; error = nbns_rq_create(NBNS_OPCODE_QUERY, prefs, &rqp); if (error) return error; bzero(&nn, sizeof(nn)); strlcpy((char *)nn.nn_name, "*", sizeof(nn.nn_name)); 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_dest = *(struct sockaddr_in *)(void *)targethost; rqp->nr_dest.sin_port = htons(NBNS_UDP_PORT_137); if (rqp->nr_dest.sin_addr.s_addr == INADDR_ANY) rqp->nr_dest.sin_addr.s_addr = htonl(INADDR_BROADCAST); error = nbns_rq_prepare(rqp); if (error) { nbns_rq_done(rqp); return error; } for (;;) { error = nbns_rq_run(rqp, cancel); if (error) break; if (rqp->nr_rpancount == 0) { error = EHOSTUNREACH; break; } error = nbns_rq_getrr(rqp, &rr); if (error) break; nrcount = (unsigned char)(*(rr.rr_data)); rr.rr_data++; for (i = 1, nrp = (struct nbns_nr *)(void *)rr.rr_data; i <= nrcount; ++i, ++nrp) { uint16_t nbFlags = betohs(nrp->nr_beflags); /* * They want all the names in the format it came into from the * network. Copy in the NetBIOS name and then the flags. */ if (nbrrArray) { CFMutableDataRef addressData = CFDataCreateMutable(NULL, 0); CFDataAppendBytes(addressData, (const UInt8 *)nrp->nr_name, (CFIndex)NB_NAMELEN); CFDataAppendBytes(addressData, (const UInt8 *)&nbFlags, (CFIndex)sizeof(uint16_t)); CFArrayAppendValue(nbrrArray, addressData); CFRelease(addressData); } nrtype = nrp->nr_name[NB_NAMELEN-1]; /* Terminate the string: */ nrp->nr_name[NB_NAMELEN-1] = (char)0; /* Strip off trailing spaces */ for (cp = &nrp->nr_name[NB_NAMELEN-2]; cp >= nrp->nr_name; --cp) { if (*cp != (char)0x20) break; *cp = (char)0; } if (nbFlags & NBNS_GROUPFLG) { if (!foundgroup || (foundgroup != NBT_WKSTA+1 && nrtype == NBT_WKSTA)) { if (workgroup) smb_optstrncpy(workgroup, nrp->nr_name, SMB_MAXNetBIOSNAMELEN); foundgroup = nrtype+1; } } else { /* Track at least ONE name, in case no server name is found */ retname = nrp->nr_name; } if (nrtype == NBT_SERVER) { if (nbt_server) smb_optstrncpy(nbt_server, nrp->nr_name, SMB_MAXNetBIOSNAMELEN); foundserver = 1; } } if (!foundserver && nbt_server) smb_optstrncpy(nbt_server, retname, SMB_MAXNetBIOSNAMELEN); ctx->nb_sender = rqp->nr_sender; break; } nbns_rq_done(rqp); return error; }