/* Unix SMB/Netbios implementation. Version 1.9. NBT netbios routines and daemon - version 2 Copyright (C) Andrew Tridgell 1994-1997 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. Module name: nameservreply.c Revision History: 14 jan 96: lkcl@pires.co.uk added multiple workgroup domain master support 04 jul 96: lkcl@pires.co.uk created module nameservreply containing NetBIOS reply functions */ #include "includes.h" extern int ClientNMB; extern int DEBUGLEVEL; extern struct in_addr wins_ip; /**************************************************************************** send a registration / release response: pos/neg **************************************************************************/ static void send_name_response(int fd, struct in_addr from_ip, int name_trn_id, int opcode, BOOL success, BOOL recurse, struct nmb_name *reply_name, int nb_flags, int ttl, struct in_addr ip) { char rdata[6]; struct packet_struct p; int rcode = 0; if (success == False) { /* NEGATIVE RESPONSE */ rcode = 6; } else if (opcode == NMB_REG && recurse == False) { /* END-NODE CHALLENGE REGISTRATION RESPONSE */ rcode = 0; } rdata[0] = nb_flags; rdata[1] = 0; putip(&rdata[2],(char *)&ip); p.ip = from_ip; p.port = NMB_PORT; p.fd = fd; p.timestamp = time(NULL); p.packet_type = NMB_PACKET; reply_netbios_packet(&p,name_trn_id, rcode,opcode,opcode,recurse, reply_name, 0x20, 0x1, ttl, rdata, 6); } /**************************************************************************** add a netbios entry. respond to the (possibly new) owner. **************************************************************************/ void add_name_respond(struct subnet_record *d, int fd, struct in_addr from_ip, uint16 response_id, struct nmb_name *name, int nb_flags, int ttl, struct in_addr register_ip, BOOL new_owner, struct in_addr reply_to_ip) { /* register the old or the new owners' ip */ add_netbios_entry(d,name->name,name->name_type, nb_flags,ttl,REGISTER,register_ip,False,True); /* reply yes or no to the host that requested the name */ send_name_response(fd,from_ip, response_id, NMB_REG, new_owner, False, name, nb_flags, ttl, reply_to_ip); } /**************************************************************************** reply to a name release ****************************************************************************/ void reply_name_release(struct packet_struct *p) { struct nmb_packet *nmb = &p->packet.nmb; struct in_addr ip; int nb_flags = nmb->additional->rdata[0]; BOOL bcast = nmb->header.nm_flags.bcast; struct name_record *n; struct subnet_record *d = NULL; int search = 0; BOOL success = False; putip((char *)&ip,&nmb->additional->rdata[2]); DEBUG(3,("Name release on name %s\n", namestr(&nmb->question.question_name))); if (!(d = find_req_subnet(p->ip, bcast))) { DEBUG(3,("response packet: bcast %s not known\n", inet_ntoa(p->ip))); return; } if (bcast) search |= FIND_LOCAL; else search |= FIND_WINS; n = find_name_search(&d, &nmb->question.question_name, search, ip); /* XXXX under what conditions should we reject the removal?? */ /* For now - remove if the names match and the group bit matches. */ if (n && (n->source != SELF) && (NAME_GROUP(n->ip_flgs[0].nb_flags) == NAME_GROUP(nb_flags))) { success = True; DEBUG(5, ("reply_name_release: Removing name %s on subnet %s\n", namestr(&nmb->question.question_name), inet_ntoa(d->bcast_ip))); remove_name(d,n); n = NULL; } if (bcast) return; /* Send a NAME RELEASE RESPONSE (pos/neg) see rfc1002.txt 4.2.10-11 */ send_name_response(p->fd,p->ip, nmb->header.name_trn_id, NMB_REL, success, nmb->header.nm_flags.recursion_desired, &nmb->question.question_name, nb_flags, 0, ip); } /**************************************************************************** reply to a reg request **************************************************************************/ void reply_name_reg(struct packet_struct *p) { struct nmb_packet *nmb = &p->packet.nmb; struct nmb_name *question = &nmb->question.question_name; struct nmb_name *reply_name = question; char *qname = question->name; int qname_type = question->name_type; BOOL bcast = nmb->header.nm_flags.bcast; int ttl = GET_TTL(nmb->additional->ttl); int nb_flags = nmb->additional->rdata[0]; BOOL group = NAME_GROUP(nb_flags); struct subnet_record *d = NULL; struct name_record *n = NULL; BOOL success = True; BOOL secured_redirect = False; struct in_addr ip, from_ip; int search = 0; putip((char *)&from_ip,&nmb->additional->rdata[2]); ip = from_ip; DEBUG(3,("Name registration for name %s at %s - ", namestr(question),inet_ntoa(ip))); if (group) { /* apparently we should return 255.255.255.255 for group queries (email from MS) */ ip = *interpret_addr2("255.255.255.255"); } if (!(d = find_req_subnet(p->ip, bcast))) { DEBUG(3,("reply_name_reg: subnet %s not known\n", inet_ntoa(p->ip))); return; } if (bcast) search |= FIND_LOCAL; else search |= FIND_WINS; /* see if the name already exists */ n = find_name_search(&d, question, search, from_ip); if (n) { DEBUG(3,("found\n")); if (!group) /* unique names */ { if (n->source == SELF || NAME_GROUP(n->ip_flgs[0].nb_flags)) { /* no-one can register one of samba's names, nor can they register a name that's a group name as a unique name */ success = False; } else if(!ip_equal(ip, n->ip_flgs[0].ip)) { /* XXXX rfc1001.txt says: * if we are doing secured WINS, we must send a Wait-Acknowledge * packet (WACK) to the person who wants the name, then do a * name query on the person who currently owns the unique name. * if the current owner still says they own it, the person who wants * the name can't have it. if they do not, or are not alive, they can. */ secured_redirect = True; reply_name = &n->name; } else { n->ip_flgs[0].ip = ip; n->death_time = ttl?p->timestamp+ttl*3:0; DEBUG(3,("%s owner: %s\n",namestr(&n->name),inet_ntoa(n->ip_flgs[0].ip))); } } else { /* refresh the name */ if (n->source != SELF) { n->death_time = ttl?p->timestamp + ttl*3:0; } } /* XXXX bug reported by terryt@ren.pc.athabascau.ca */ /* names that people have checked for and not found get DNSFAILed. we need to update the name record if someone then registers */ if (n->source == DNSFAIL) n->source = REGISTER; } else { DEBUG(3,("not found\n")); /* add the name to our name/subnet, or WINS, database */ n = add_netbios_entry(d,qname,qname_type,nb_flags,ttl,REGISTER,ip, True,!bcast); } /* if samba owns a unique name on a subnet, then it must respond and disallow the attempted registration. if the registration is successful by broadcast, only then is there no need to respond (implicit registration: see rfc1001.txt 15.2.1). */ if (bcast && success) return; if (secured_redirect) { char rdata[2]; /* XXXX i am confused. RSVAL or SSVAL? assume NMB byte ordering */ RSSVAL(rdata,0,(nmb->header.opcode&0xf) + ((nb_flags&0xff) << 4)); /* XXXX mistake in rfc1002.txt? 4.2.16: NULL is 0xa see 4.2.1.3 type = 0x0a; see rfc1002.txt 4.2.1.3 class = 0x01; see rfc1002.txt 4.2.16 */ /* send WAIT ACKNOWLEDGEMENT see rfc1002.txt 4.2.16 */ reply_netbios_packet(p,nmb->header.name_trn_id, 0,NMB_WAIT_ACK,NMB_WAIT_ACK,False, reply_name, 0x0a, 0x01, 15*1000, /* 15 seconds long enough to wait? */ rdata, 2); /* initiate some enquiries to the current owner. */ queue_netbios_packet(d,ClientNMB,NMB_QUERY, NAME_REGISTER_CHALLENGE, reply_name->name,reply_name->name_type, nb_flags,0,0,NULL,NULL, False, False, n->ip_flgs[0].ip, p->ip); } else { /* Send a NAME REGISTRATION RESPONSE (pos/neg) see rfc1002.txt 4.2.13-14 or an END-NODE CHALLENGE REGISTRATION RESPONSE see rfc1002.txt 4.2.7 */ send_name_response(p->fd,p->ip, nmb->header.name_trn_id, NMB_REG, success, nmb->header.nm_flags.recursion_desired, reply_name, nb_flags, ttl, ip); } } /* this is used to sort names for a name status into a sensible order we put our own names first, then in alphabetical order */ static int status_compare(char *n1,char *n2) { extern pstring myname; int l1,l2,l3; /* its a bit tricky because the names are space padded */ for (l1=0;l1<15 && n1[l1] && n1[l1] != ' ';l1++) ; for (l2=0;l2<15 && n2[l2] && n2[l2] != ' ';l2++) ; l3 = strlen(myname); if ((l1==l3) && strncmp(n1,myname,l3) == 0 && (l2!=l3 || strncmp(n2,myname,l3) != 0)) return -1; if ((l2==l3) && strncmp(n2,myname,l3) == 0 && (l1!=l3 || strncmp(n1,myname,l3) != 0)) return 1; return memcmp(n1,n2,18); } /**************************************************************************** reply to a name status query combine the list of the local interface on which the query was made with the names registered via wins. ****************************************************************************/ void reply_name_status(struct packet_struct *p) { struct nmb_packet *nmb = &p->packet.nmb; char *qname = nmb->question.question_name.name; int ques_type = nmb->question.question_name.name_type; char rdata[MAX_DGRAM_SIZE]; char *countptr, *buf, *bufend, *buf0; int names_added,i; struct name_record *n; struct subnet_record *d = NULL; int search = FIND_SELF | FIND_WINS | FIND_LOCAL; /* NOTE: we always treat a name status lookup as a bcast */ if (!(d = find_req_subnet(p->ip, True))) { DEBUG(3,("Name status req: bcast %s not known\n", inet_ntoa(p->ip))); return; } DEBUG(3,("Name status for name %s %s\n", namestr(&nmb->question.question_name), inet_ntoa(p->ip))); n = find_name_search(&d, &nmb->question.question_name, search, p->ip); if (!n) return; /* XXXX hack, we should calculate exactly how many will fit */ bufend = &rdata[MAX_DGRAM_SIZE] - 18; countptr = buf = rdata; buf += 1; buf0 = buf; names_added = 0; n = d->namelist; while (buf < bufend) { if (n->source == SELF) { int name_type = n->name.name_type; /* check if we want to exclude other workgroup names from the response. if we don't exclude them, windows clients get confused and will respond with an error for NET VIEW */ if (!strequal(n->name.name,"*") && !strequal(n->name.name,"__SAMBA__") && (name_type < 0x1b || name_type >= 0x20 || ques_type < 0x1b || ques_type >= 0x20 || strequal(qname, n->name.name))) { /* start with first bit of putting info in buffer: the name */ bzero(buf,18); sprintf(buf,"%-15.15s",n->name.name); strupper(buf); /* put name type and netbios flags in buffer */ buf[15] = name_type; buf[16] = n->ip_flgs[0].nb_flags; buf += 18; names_added++; } } /* remove duplicate names */ qsort(buf0,names_added,18,QSORT_CAST status_compare); for (i=1;inext; if (!n) { /* end of this name list: add wins names too? */ struct subnet_record *w_d; if (!(w_d = wins_subnet)) break; if (w_d != d) { d = w_d; n = d->namelist; /* start on the wins name list */ } } if (!n) break; } SCVAL(countptr,0,names_added); /* XXXXXXX we should fill in more fields of the statistics structure */ bzero(buf,64); { extern int num_good_sends,num_good_receives; SIVAL(buf,20,num_good_sends); SIVAL(buf,24,num_good_receives); } buf += 46; /* Send a POSITIVE NAME STATUS RESPONSE */ reply_netbios_packet(p,nmb->header.name_trn_id, 0,NMB_STATUS,0,True, &nmb->question.question_name, 0x21, 0x01, 0, rdata,PTR_DIFF(buf,rdata)); } /*************************************************************************** reply to a name query. with broadcast name queries: - only reply if the query is for one of YOUR names. all other machines on the network will be doing the same thing (that is, only replying to a broadcast query if they own it) NOTE: broadcast name queries should only be sent out by a machine if they HAVEN'T been configured to use WINS. this is generally bad news in a wide area tcp/ip network and should be rectified by the systems administrator. USE WINS! :-) - the exception to this is if the query is for a Primary Domain Controller type name (0x1b), in which case, a reply is sent. - NEVER send a negative response to a broadcast query. no-one else will! with directed name queries: - if you are the WINS server, you are expected to respond with either a negative response, a positive response, or a wait-for-acknowledgement packet, and then later on a pos/neg response. ****************************************************************************/ void reply_name_query(struct packet_struct *p) { struct nmb_packet *nmb = &p->packet.nmb; struct nmb_name *question = &nmb->question.question_name; int name_type = question->name_type; BOOL bcast = nmb->header.nm_flags.bcast; int ttl=0; int rcode = 0; int nb_flags = 0; struct in_addr retip; char rdata[6]; struct subnet_record *d = NULL; BOOL success = True; struct name_record *n = NULL; /* directed queries are for WINS server: broadcasts are local SELF queries. the exception is Domain Master names. */ int search = bcast ? FIND_LOCAL | FIND_WINS: FIND_WINS; if (search & FIND_LOCAL) { if (!(d = find_req_subnet(p->ip, bcast))) { DEBUG(3,("name query: bcast %s not known\n", inet_ntoa(p->ip))); success = False; } } else { if (!(d = wins_subnet)) { DEBUG(3,("name query: wins search %s not known\n", inet_ntoa(p->ip))); success = False; } } DEBUG(3,("Name query from %s for name %s<0x%x>\n", inet_ntoa(p->ip), question->name, question->name_type)); if (search == 0) { /* eh? no criterion for searching database. help! */ success = False; } if (!bcast && (name_type == 0x1d) && lp_wins_support()) { /* see WINS manager HELP - 'How WINS Handles Special Names' */ /* a WINS query (unicasted) for a 0x1d name must always return False */ success = False; } if (success) { /* look up the name in the cache */ n = find_name_search(&d, question, search, p->ip); /* it is a name that already failed DNS lookup or it's expired */ if (n && (n->source == DNSFAIL || (n->death_time && n->death_time < p->timestamp))) { success = False; } /* do we want to do dns lookups? */ /* XXXX this DELAYS nmbd while it does a search. not a good idea but there's no pleasant alternative. phil@hands.com suggested making the name a full DNS name, which would succeed / fail much quicker. */ if (success && !n && (lp_wins_proxy() || !bcast)) { n = dns_name_search(question, p->timestamp); } } if (!n) success = False; if (success) { if (bcast && n->source != SELF && name_type != 0x1b) { /* don't respond to broadcast queries unless the query is for a name we own or it is for a Primary Domain Controller name */ if (!lp_wins_proxy() || same_net(p->ip,n->ip_flgs[0].ip,*iface_nmask(p->ip))) { /* never reply with a negative response to broadcast queries */ return; } } /* name is directed query, or it's self, or it's a Domain Master type name, or we're replying on behalf of a caller because they are on a different subnet and cannot hear the broadcast. XXXX lp_wins_proxy should be switched off in environments where broadcasts are forwarded */ /* XXXX note: for proxy servers, we should forward the query on to another WINS server if the name is not in our database, or we are not a WINS server ourselves */ ttl = n->death_time ? n->death_time - p->timestamp : GET_TTL(0); retip = n->ip_flgs[0].ip; nb_flags = n->ip_flgs[0].nb_flags; } if (!success && bcast) return; /* never reply negative response to bcasts */ /* if the IP is 0 then substitute my IP */ if (zero_ip(retip)) retip = *iface_ip(p->ip); /* SPECIAL CASE... If we are a WINS server and the request is explicitly *to* the WINS server and the name type is WORKGROUP<0x1e> we should respond with the local broadcast address 255.255.255.255. */ if(!bcast && (name_type == 0x1e) && lp_wins_support()) retip = *interpret_addr2("255.255.255.255"); if (success) { rcode = 0; DEBUG(3,("OK %s\n",inet_ntoa(retip))); } else { rcode = 3; DEBUG(3,("UNKNOWN\n")); } if (success) { rdata[0] = nb_flags; rdata[1] = 0; putip(&rdata[2],(char *)&retip); } reply_netbios_packet(p,nmb->header.name_trn_id, rcode,NMB_QUERY,0,True, &nmb->question.question_name, 0x20, 0x01, ttl, rdata, success ? 6 : 0); }