From 64f0348a3f994334abe64a4d4896109c3c8c9039 Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Sat, 13 Dec 1997 14:16:07 +0000 Subject: This is it ! The mega-merge of the JRA_NMBD_REWRITE branch back into the main tree. For the cvs logs of all the files starting nmbd_*.c, look in the JRA_NMBD_REWRITE branch. That branch has now been discontinued. Jeremy. (This used to be commit d80b0cb645f81d16734929a0b27a91c6650499bb) --- source3/nmbd/nmbd_winsserver.c | 1565 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1565 insertions(+) create mode 100644 source3/nmbd/nmbd_winsserver.c (limited to 'source3/nmbd/nmbd_winsserver.c') diff --git a/source3/nmbd/nmbd_winsserver.c b/source3/nmbd/nmbd_winsserver.c new file mode 100644 index 0000000000..ba7b62e5ab --- /dev/null +++ b/source3/nmbd/nmbd_winsserver.c @@ -0,0 +1,1565 @@ +/* + Unix SMB/Netbios implementation. + Version 1.9. + NBT netbios routines and daemon - version 2 + + Copyright (C) Jeremy Allison 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. + +*/ + +#include "includes.h" + +#define WINS_LIST "wins.dat" + +extern int DEBUGLEVEL; +extern struct in_addr ipzero; + +/**************************************************************************** +Determine if this packet should be allocated to the WINS server. +*****************************************************************************/ + +BOOL packet_is_for_wins_server(struct packet_struct *packet) +{ + struct nmb_packet *nmb = &packet->packet.nmb; + + /* Only unicast packets go to a WINS server. */ + if((wins_server_subnet == NULL) || (nmb->header.nm_flags.bcast == True)) + { + DEBUG(10, ("packet_is_for_wins_server: failing WINS test #1.\n")); + return False; + } + + /* Check for node status requests. */ + if (nmb->question.question_type != QUESTION_TYPE_NB_QUERY) + return False; + + switch(nmb->header.opcode) + { + /* + * A WINS server issues WACKS, not receives them. + */ + case NMB_WACK_OPCODE: + DEBUG(10, ("packet_is_for_wins_server: failing WINS test #2 (WACK).\n")); + return False; + /* + * A WINS server only processes registration and + * release requests, not responses. + */ + case NMB_NAME_REG_OPCODE: + case NMB_NAME_MULTIHOMED_REG_OPCODE: + case NMB_NAME_REFRESH_OPCODE_8: /* ambiguity in rfc1002 about which is correct. */ + case NMB_NAME_REFRESH_OPCODE_9: /* WinNT uses 8 by default. */ + if(nmb->header.response) + { + DEBUG(10, ("packet_is_for_wins_server: failing WINS test #3 (response = 1).\n")); + return False; + } + break; + + case NMB_NAME_RELEASE_OPCODE: + if(nmb->header.response) + { + DEBUG(10, ("packet_is_for_wins_server: failing WINS test #4 (response = 1).\n")); + return False; + } + break; + + /* + * Only process unicast name queries with rd = 1. + */ + case NMB_NAME_QUERY_OPCODE: + if(!nmb->header.response && !nmb->header.nm_flags.recursion_desired) + { + DEBUG(10, ("packet_is_for_wins_server: failing WINS test #5 (response = 1).\n")); + return False; + } + break; + } + + return True; +} + +/**************************************************************************** +Utility function to decide what ttl to give a register/refresh request. +*****************************************************************************/ + +static int get_ttl_from_packet(struct nmb_packet *nmb) +{ + int ttl = nmb->additional->ttl; + + if(ttl < lp_min_wins_ttl() ) + ttl = lp_min_wins_ttl(); + + if(ttl > lp_max_wins_ttl() ) + ttl = lp_max_wins_ttl(); + + return ttl; +} + +/**************************************************************************** +Load or create the WINS database. +*****************************************************************************/ + +BOOL initialise_wins(void) +{ + fstring fname; + time_t time_now = time(NULL); + FILE *fp; + pstring line; + + if(!lp_we_are_a_wins_server()) + return True; + + add_samba_names_to_subnet(wins_server_subnet); + +#ifndef SYNC_DNS + /* Setup the async dns. */ + start_async_dns(); +#endif + + fstrcpy(fname,lp_lockdir()); + trim_string(fname,NULL,"/"); + strcat(fname,"/"); + strcat(fname,WINS_LIST); + + if((fp = fopen(fname,"r")) == NULL) + { + DEBUG(2,("initialise_wins: Can't open wins database file %s. Error was %s\n", + fname, strerror(errno) )); + return True; + } + + while (!feof(fp)) + { + pstring name_str, ip_str, ttl_str, nb_flags_str; + unsigned int num_ips; + pstring name; + struct in_addr *ip_list; + int type = 0; + uint16 nb_flags; + time_t ttl; + enum name_source source; + char *ptr; + char *p; + BOOL got_token; + BOOL was_ip; + int i; + + /* Read a line from the wins.dat file. Strips whitespace + from the beginning and end of the line. + */ + if (!fgets_slash(line,sizeof(pstring),fp)) + continue; + + if (*line == '#') + continue; + + ptr = line; + + /* + * Now we handle multiple IP addresses per name we need + * to iterate over the line twice. The first time to + * determine how many IP addresses there are, the second + * time to actually parse them into the ip_list array. + */ + + if (!next_token(&ptr,name_str,NULL)) + { + DEBUG(0,("initialise_wins: Failed to parse name when parsing line %s\n", line )); + continue; + } + + if (!next_token(&ptr,ttl_str,NULL)) + { + DEBUG(0,("initialise_wins: Failed to parse time to live when parsing line %s\n", line )); + continue; + } + + /* + * Determine the number of IP addresses per line. + */ + num_ips = 0; + do + { + got_token = next_token(&ptr,ip_str,NULL); + was_ip = False; + + if(got_token && strchr(ip_str, '.')) + { + num_ips++; + was_ip = True; + } + } while( got_token && was_ip); + + if(num_ips == 0) + { + DEBUG(0,("initialise_wins: Missing IP address when parsing line %s\n", line )); + continue; + } + + if(!got_token) + { + DEBUG(0,("initialise_wins: Missing nb_flags when parsing line %s\n", line )); + continue; + } + + /* Allocate the space for the ip_list. */ + if((ip_list = (struct in_addr *)malloc( num_ips * sizeof(struct in_addr))) == NULL) + { + DEBUG(0,("initialise_wins: Malloc fail !\n")); + return False; + } + + /* Reset and re-parse the line. */ + ptr = line; + next_token(&ptr,name_str,NULL); + next_token(&ptr,ttl_str,NULL); + for(i = 0; i < num_ips; i++) + { + next_token(&ptr, ip_str, NULL); + ip_list[i] = *interpret_addr2(ip_str); + if (ip_equal(ip_list[i], ipzero)) + source = SELF_NAME; + } + next_token(&ptr,nb_flags_str,NULL); + + /* + * Deal with SELF or REGISTER name encoding. Default is REGISTER + * for compatibility with old nmbds. + */ + + if(nb_flags_str[strlen(nb_flags_str)-1] == 'S') + { + DEBUG(5,("initialise_wins: Ignoring SELF name %s\n", line)); + free((char *)ip_list); + continue; + } + + if(nb_flags_str[strlen(nb_flags_str)-1] == 'R') + nb_flags_str[strlen(nb_flags_str)-1] = '\0'; + + /* Netbios name. # divides the name from the type (hex): netbios#xx */ + pstrcpy(name,name_str); + + if((p = strchr(name,'#')) != NULL) + { + *p = 0; + sscanf(p+1,"%x",&type); + } + + /* Decode the netbios flags (hex) and the time-to-live (in seconds). */ + sscanf(nb_flags_str,"%hx",&nb_flags); + sscanf(ttl_str,"%ld",&ttl); + + /* add all entries that have 60 seconds or more to live */ + if ((ttl - 60) > time_now || ttl == PERMANENT_TTL) + { + struct name_record *namerec; + + if(ttl != PERMANENT_TTL) + ttl -= time_now; + + DEBUG(4, ("initialise_wins: add name: %s#%02x ttl = %ld first IP %s flags = %2hx\n", + name, type, ttl, inet_ntoa(ip_list[0]), nb_flags)); + + namerec = add_name_to_subnet(wins_server_subnet, name, type, nb_flags, + ttl, REGISTER_NAME, num_ips, ip_list); + + } + else + { + DEBUG(4, ("initialise_wins: not adding name (ttl problem) %s#%02x ttl = %ld first IP %s flags = %2hx\n", + name, type, ttl, inet_ntoa(ip_list[0]), nb_flags)); + } + + free((char *)ip_list); + } + + fclose(fp); + return True; +} + +/**************************************************************************** +Send a WINS WACK (Wait ACKnowledgement) response. +**************************************************************************/ + +static void send_wins_wack_response(int ttl, struct packet_struct *p) +{ + struct nmb_packet *nmb = &p->packet.nmb; + unsigned char rdata[2]; + + rdata[0] = rdata[1] = 0; + + /* Taken from nmblib.c - we need to send back almost + identical bytes from the requesting packet header. */ + + rdata[0] = (nmb->header.opcode & 0xF) << 3; + if (nmb->header.nm_flags.authoritative && + nmb->header.response) rdata[0] |= 0x4; + if (nmb->header.nm_flags.trunc) rdata[0] |= 0x2; + if (nmb->header.nm_flags.recursion_desired) rdata[0] |= 0x1; + if (nmb->header.nm_flags.recursion_available && + nmb->header.response) rdata[1] |= 0x80; + if (nmb->header.nm_flags.bcast) rdata[1] |= 0x10; + + reply_netbios_packet(p, /* Packet to reply to. */ + 0, /* Result code. */ + NMB_WAIT_ACK, /* nmbd type code. */ + NMB_WACK_OPCODE, /* opcode. */ + ttl, /* ttl. */ + (char *)rdata, /* data to send. */ + 2); /* data length. */ +} + +/**************************************************************************** +Send a WINS name registration response. +**************************************************************************/ + +static void send_wins_name_registration_response(int rcode, int ttl, struct packet_struct *p) +{ + struct nmb_packet *nmb = &p->packet.nmb; + char rdata[6]; + + memcpy(&rdata[0], &nmb->additional->rdata[0], 6); + + reply_netbios_packet(p, /* Packet to reply to. */ + rcode, /* Result code. */ + WINS_REG, /* nmbd type code. */ + NMB_NAME_REG_OPCODE, /* opcode. */ + ttl, /* ttl. */ + rdata, /* data to send. */ + 6); /* data length. */ +} + +/*********************************************************************** + Deal with a name refresh request to a WINS server. +************************************************************************/ + +void wins_process_name_refresh_request(struct subnet_record *subrec, + struct packet_struct *p) +{ + struct nmb_packet *nmb = &p->packet.nmb; + struct nmb_name *question = &nmb->question.question_name; + BOOL bcast = nmb->header.nm_flags.bcast; + uint16 nb_flags = get_nb_flags(nmb->additional->rdata); + BOOL group = (nb_flags & NB_GROUP) ? True : False; + struct name_record *namerec = NULL; + int ttl = get_ttl_from_packet(nmb); + struct in_addr from_ip; + + putip((char *)&from_ip,&nmb->additional->rdata[2]); + + if(bcast) + { + /* + * We should only get unicast name refresh packets here. + * Anyone trying to refresh broadcast should not be going to a WINS + * server. Log an error here. + */ + + DEBUG(0,("wins_process_name_refresh_request: broadcast name refresh request \ +received for name %s from IP %s on subnet %s. Error - should not be sent to WINS server\n", + namestr(question), inet_ntoa(from_ip), subrec->subnet_name)); + return; + } + + DEBUG(3,("wins_process_name_refresh_request: Name refresh for name %s \ +IP %s\n", namestr(question), inet_ntoa(from_ip) )); + + /* + * See if the name already exists. + */ + + namerec = find_name_on_subnet(subrec, question, FIND_ANY_NAME); + + /* + * If this is a refresh request and the name doesn't exist then + * fail it. + */ + + if(namerec == NULL) + { + DEBUG(3,("wins_process_name_refresh_request: Name refresh for name %s and \ +the name does not exist.\n", namestr(question) )); + send_wins_name_registration_response(NAM_ERR, 0, p); + return; + } + + /* + * Check that the group bits for the refreshing name and the + * name in our database match. + */ + + if((namerec != NULL) && ((group && !NAME_GROUP(namerec)) || (!group && NAME_GROUP(namerec))) ) + { + DEBUG(3,("wins_process_name_refresh_request: Name %s group bit = %s \ +does not match group bit in WINS for this name.\n", namestr(question), group ? "True" : "False" )); + send_wins_name_registration_response(RFS_ERR, 0, p); + return; + } + + /* + * For a unique name check that the person refreshing the name is one of the registered IP + * addresses. If not - fail the refresh. Do the same for group names with a type of 0x1c. + * Just return success for unique 0x1d refreshes. For normal group names update the ttl + * and return success. + */ + + if((!group || (group && (question->name_type == 0x1c))) && find_ip_in_name_record(namerec, from_ip )) + { + /* + * Update the ttl. + */ + update_name_ttl(namerec, ttl); + send_wins_name_registration_response(0, ttl, p); + return; + } + else if(group) + { + /* + * Normal groups are all registered with an IP address of 255.255.255.255 + * so we can't search for the IP address. + */ + update_name_ttl(namerec, ttl); + send_wins_name_registration_response(0, ttl, p); + return; + } + else if(!group && (question->name_type == 0x1d)) + { + /* + * Special name type - just pretend the refresh succeeded. + */ + send_wins_name_registration_response(0, ttl, p); + return; + } + else + { + /* + * Fail the refresh. + */ + + DEBUG(3,("wins_process_name_refresh_request: Name refresh for name %s with IP %s and \ +is IP is not known to the name.\n", namestr(question), inet_ntoa(from_ip) )); + send_wins_name_registration_response(RFS_ERR, 0, p); + return; + } +} + +/*********************************************************************** + Deal with a name registration request query success to a client that + owned the name. + + We have a locked pointer to the original packet stashed away in the + userdata pointer. The success here is actually a failure as it means + the client we queried wants to keep the name, so we must return + a registration failure to the original requestor. +************************************************************************/ + +static void wins_register_query_success(struct subnet_record *subrec, + struct userdata_struct *userdata, + struct nmb_name *question_name, + struct in_addr ip, + struct res_rec *answers) +{ + struct packet_struct *orig_reg_packet; + + memcpy((char *)&orig_reg_packet, userdata->data, sizeof(struct packet_struct *)); + + DEBUG(3,("wins_register_query_success: Original client at IP %s still wants the \ +name %s. Rejecting registration request.\n", inet_ntoa(ip), namestr(question_name) )); + + send_wins_name_registration_response(RFS_ERR, 0, orig_reg_packet); + + orig_reg_packet->locked = False; + free_packet(orig_reg_packet); +} + +/*********************************************************************** + Deal with a name registration request query failure to a client that + owned the name. + + We have a locked pointer to the original packet stashed away in the + userdata pointer. The failure here is actually a success as it means + the client we queried didn't want to keep the name, so we can remove + the old name record and then successfully add the new name. +************************************************************************/ + +static void wins_register_query_fail(struct subnet_record *subrec, + struct response_record *rrec, + struct nmb_name *question_name, + int rcode) +{ + struct userdata_struct *userdata = rrec->userdata; + struct packet_struct *orig_reg_packet; + struct nmb_packet *nmb; + struct name_record *namerec = NULL; + uint16 nb_flags; + BOOL group; + + memcpy((char *)&orig_reg_packet, userdata->data, sizeof(struct packet_struct *)); + + nmb = &orig_reg_packet->packet.nmb; + + nb_flags = get_nb_flags(nmb->additional->rdata); + group = (nb_flags & NB_GROUP) ? True : False; + + /* + * We want to just add the name, as we now know the original owner + * didn't want it. But we can't just do that as an arbitary + * amount of time may have taken place between the name query + * request and this timeout/error response. So we check that + * the name still exists and is in the same state - if so + * we remove it and call wins_process_name_registration_request() + * as we know it will do the right thing now. + */ + + namerec = find_name_on_subnet(subrec, question_name, FIND_ANY_NAME); + + if((namerec != NULL) && (namerec->source == REGISTER_NAME) && + ip_equal(rrec->packet->ip, *namerec->ip) ) + { + remove_name_from_namelist( subrec, namerec); + namerec = NULL; + } + + if(namerec == NULL) + wins_process_name_registration_request(subrec, orig_reg_packet); + else + DEBUG(2,("wins_register_query_fail: The state of the WINS database changed between \ +querying for name %s in order to replace it and this reply.\n", namestr(question_name) )); + + orig_reg_packet->locked = False; + free_packet(orig_reg_packet); +} + +/*********************************************************************** + Deal with a name registration request to a WINS server. + + Use the following pseudocode : + + registering_group + | + | + +--------name exists + | | + | | + | +--- existing name is group + | | | + | | | + | | +--- add name (return). + | | + | | + | +--- exiting name is unique + | | + | | + | +--- query existing owner (return). + | + | + +--------name doesn't exist + | + | + +--- add name (return). + + registering_unique + | + | + +--------name exists + | | + | | + | +--- existing name is group + | | | + | | | + | | +--- fail add (return). + | | + | | + | +--- exiting name is unique + | | + | | + | +--- query existing owner (return). + | + | + +--------name doesn't exist + | + | + +--- add name (return). + + As can be seen from the above, the two cases may be collapsed onto each + other with the exception of the case where the name already exists and + is a group name. This case we handle with an if statement. + +************************************************************************/ + +void wins_process_name_registration_request(struct subnet_record *subrec, + struct packet_struct *p) +{ + struct nmb_packet *nmb = &p->packet.nmb; + struct nmb_name *question = &nmb->question.question_name; + BOOL bcast = nmb->header.nm_flags.bcast; + uint16 nb_flags = get_nb_flags(nmb->additional->rdata); + int ttl = get_ttl_from_packet(nmb); + struct name_record *namerec = NULL; + struct in_addr from_ip; + BOOL registering_group_name = (nb_flags & NB_GROUP) ? True : False;; + + putip((char *)&from_ip,&nmb->additional->rdata[2]); + + if(bcast) + { + /* + * We should only get unicast name registration packets here. + * Anyone trying to register broadcast should not be going to a WINS + * server. Log an error here. + */ + + DEBUG(0,("wins_process_name_registration_request: broadcast name registration request \ +received for name %s from IP %s on subnet %s. Error - should not be sent to WINS server\n", + namestr(question), inet_ntoa(from_ip), subrec->subnet_name)); + return; + } + + DEBUG(3,("wins_process_name_registration_request: %s name registration for name %s \ +IP %s\n", registering_group_name ? "Group" : "Unique", namestr(question), inet_ntoa(from_ip) )); + + /* + * See if the name already exists. + */ + + namerec = find_name_on_subnet(subrec, question, FIND_ANY_NAME); + + /* + * Deal with the case where the name found was a dns entry. + * Remove it as we now have a NetBIOS client registering the + * name. + */ + + if((namerec != NULL) && ((namerec->source == DNS_NAME) || (namerec->source == DNSFAIL_NAME))) + { + DEBUG(5,("wins_process_name_registration_request: Name (%s) in WINS was a dns lookup \ +- removing it.\n", namestr(question) )); + remove_name_from_namelist( subrec, namerec); + namerec = NULL; + } + + /* + * Reject if the name exists and is not a REGISTER_NAME. + * (ie. Don't allow any static names to be overwritten. + */ + + if((namerec != NULL) && (namerec->source != REGISTER_NAME)) + { + DEBUG(3,("wins_process_name_registration_request: Attempt to register name %s. Name \ +already exists in WINS with source type %d.\n", namestr(question), namerec->source )); + send_wins_name_registration_response(RFS_ERR, 0, p); + return; + } + + /* + * Special policy decisions based on MS documentation. + * 1). All group names (except names ending in 0x1c) are added as 255.255.255.255. + * 2). All unique names ending in 0x1d are ignored, although a positive response is sent. + */ + + /* + * A group name is always added as the local broadcast address, except + * for group names ending in 0x1c. + * Group names with type 0x1c are registered with individual IP addresses. + */ + + if(registering_group_name && (question->name_type != 0x1c)) + from_ip = *interpret_addr2("255.255.255.255"); + + /* + * Ignore all attempts to register a unique 0x1d name, although return success. + */ + + if(!registering_group_name && (question->name_type == 0x1d)) + { + DEBUG(3,("wins_process_name_registration_request: Ignoring request \ +to register name %s from IP %s.", namestr(question), inet_ntoa(p->ip) )); + send_wins_name_registration_response(0, ttl, p); + return; + } + + /* + * Next two cases are the 'if statement' mentioned above. + */ + + if((namerec != NULL) && NAME_GROUP(namerec)) + { + if(registering_group_name) + { + /* + * If we are adding a group name, the name exists and is also a group entry just add this + * IP address to it and update the ttl. + */ + + DEBUG(3,("wins_process_name_registration_request: Adding IP %s to group name %s.\n", + inet_ntoa(from_ip), namestr(question) )); + /* + * Check the ip address is not already in the group. + */ + if(!find_ip_in_name_record(namerec, from_ip)) + add_ip_to_name_record(namerec, from_ip); + update_name_ttl(namerec, ttl); + send_wins_name_registration_response(0, ttl, p); + return; + } + else + { + /* + * If we are adding a unique name, the name exists in the WINS db + * and is a group name then reject the registration. + */ + + DEBUG(3,("wins_process_name_registration_request: Attempt to register name %s. Name \ +already exists in WINS as a GROUP name.\n", namestr(question) )); + send_wins_name_registration_response(RFS_ERR, 0, p); + return; + } + } + + /* + * From here on down we know that if the name exists in the WINS db it is + * a unique name, not a group name. + */ + + /* + * If the name exists and is one of our names then check the + * registering IP address. If it's not one of ours then automatically + * reject without doing the query - we know we will reject it. + */ + + if((namerec != NULL) && (is_myname(namerec->name.name)) ) + { + if(!ismyip(from_ip)) + { + DEBUG(3,("wins_process_name_registration_request: Attempt to register name %s. Name \ +is one of our (WINS server) names. Denying registration.\n", namestr(question) )); + send_wins_name_registration_response(RFS_ERR, 0, p); + return; + } + else + { + /* + * It's one of our names and one of our IP's - update the ttl. + */ + update_name_ttl(namerec, ttl); + send_wins_name_registration_response(0, ttl, p); + return; + } + } + + /* + * If the name exists and it is a unique registration and the registering IP + * is the same as the the (single) already registered IP then just update the ttl. + */ + + if(!registering_group_name && (namerec != NULL) && (namerec->num_ips == 1) && + ip_equal(namerec->ip[0], from_ip)) + { + update_name_ttl(namerec, ttl); + send_wins_name_registration_response(0, ttl, p); + return; + } + + /* + * Finally if the name exists do a query to the registering machine + * to see if they still claim to have the name. + */ + + if(namerec != NULL) + { + char ud[sizeof(struct userdata_struct) + sizeof(struct packet_struct *)]; + struct userdata_struct *userdata = (struct userdata_struct *)&ud; + + /* + * First send a WACK to the registering machine. + */ + + send_wins_wack_response(60, p); + + /* + * When the reply comes back we need the original packet. + * Lock this so it won't be freed and then put it into + * the userdata structure. + */ + + p->locked = True; + + userdata = (struct userdata_struct *)ud; + + userdata->copy_fn = NULL; + userdata->free_fn = NULL; + userdata->userdata_len = sizeof(struct packet_struct *); + memcpy(userdata->data, (char *)&p, sizeof(struct packet_struct *) ); + + /* + * As query_name uses the subnet broadcast address as the destination + * of the packet we temporarily change the subnet broadcast address to + * be the first IP address of the name owner and send the packet. This + * is a *horrible* hack but the alternative is to add the destination + * address parameter to all query_name() calls. I hate this code :-). + */ + + subrec->bcast_ip = *namerec->ip; + + query_name( subrec, question->name, question->name_type, + wins_register_query_success, + wins_register_query_fail, + userdata); + subrec->bcast_ip = ipzero; + return; + } + + /* + * Name did not exist - add it. + */ + + add_name_to_subnet(subrec, question->name, question->name_type, + nb_flags, ttl, REGISTER_NAME, 1, &from_ip); + + send_wins_name_registration_response(0, ttl, p); +} + +/*********************************************************************** + Deal with a mutihomed name query success to the machine that + requested the multihomed name registration. + + We have a locked pointer to the original packet stashed away in the + userdata pointer. +************************************************************************/ + +static void wins_multihomed_register_query_success(struct subnet_record *subrec, + struct userdata_struct *userdata, + struct nmb_name *question_name, + struct in_addr ip, + struct res_rec *answers) +{ + struct packet_struct *orig_reg_packet; + struct nmb_packet *nmb; + struct name_record *namerec = NULL; + struct in_addr from_ip; + int ttl; + + memcpy((char *)&orig_reg_packet, userdata->data, sizeof(struct packet_struct *)); + + nmb = &orig_reg_packet->packet.nmb; + + putip((char *)&from_ip,&nmb->additional->rdata[2]); + ttl = get_ttl_from_packet(nmb); + + /* + * We want to just add the new IP, as we now know the requesting + * machine claims to own it. But we can't just do that as an arbitary + * amount of time may have taken place between the name query + * request and this response. So we check that + * the name still exists and is in the same state - if so + * we just add the extra IP and update the ttl. + */ + + namerec = find_name_on_subnet(subrec, question_name, FIND_ANY_NAME); + + if( (namerec == NULL) || (namerec->source != REGISTER_NAME) ) + { + DEBUG(3,("wins_multihomed_register_query_success: name %s is not in the correct state to add \ +a subsequent IP addess.\n", namestr(question_name) )); + send_wins_name_registration_response(RFS_ERR, 0, orig_reg_packet); + return; + } + + if(!find_ip_in_name_record(namerec, from_ip)) + add_ip_to_name_record(namerec, from_ip); + update_name_ttl(namerec, ttl); + send_wins_name_registration_response(0, ttl, orig_reg_packet); + +} + +/*********************************************************************** + Deal with a name registration request query failure to a client that + owned the name. + + We have a locked pointer to the original packet stashed away in the + userdata pointer. +************************************************************************/ + +static void wins_multihomed_register_query_fail(struct subnet_record *subrec, + struct response_record *rrec, + struct nmb_name *question_name, + int rcode) +{ + struct userdata_struct *userdata = rrec->userdata; + struct packet_struct *orig_reg_packet; + + memcpy((char *)&orig_reg_packet, userdata->data, sizeof(struct packet_struct *)); + + DEBUG(3,("wins_multihomed_register_query_fail: Registering machine at IP %s failed to answer \ +query successfully for name %s.\n", inet_ntoa(orig_reg_packet->ip), namestr(question_name) )); + send_wins_name_registration_response(RFS_ERR, 0, orig_reg_packet); + return; +} + +/*********************************************************************** + Deal with a multihomed name registration request to a WINS server. + These cannot be group name registrations. +***********************************************************************/ + +void wins_process_multihomed_name_registration_request( struct subnet_record *subrec, + struct packet_struct *p) +{ + struct nmb_packet *nmb = &p->packet.nmb; + struct nmb_name *question = &nmb->question.question_name; + BOOL bcast = nmb->header.nm_flags.bcast; + uint16 nb_flags = get_nb_flags(nmb->additional->rdata); + int ttl = get_ttl_from_packet(nmb); + struct name_record *namerec = NULL; + struct in_addr from_ip; + BOOL group = (nb_flags & NB_GROUP) ? True : False;; + + putip((char *)&from_ip,&nmb->additional->rdata[2]); + + if(bcast) + { + /* + * We should only get unicast name registration packets here. + * Anyone trying to register broadcast should not be going to a WINS + * server. Log an error here. + */ + + DEBUG(0,("wins_process_multihomed_name_registration_request: broadcast name registration request \ +received for name %s from IP %s on subnet %s. Error - should not be sent to WINS server\n", + namestr(question), inet_ntoa(from_ip), subrec->subnet_name)); + return; + } + + /* + * Only unique names should be registered multihomed. + */ + + if(group) + { + DEBUG(0,("wins_process_multihomed_name_registration_request: group name registration request \ +received for name %s from IP %s on subnet %s. Errror - group names should not be multihomed.\n", + namestr(question), inet_ntoa(from_ip), subrec->subnet_name)); + return; + } + + DEBUG(3,("wins_process_multihomed_name_registration_request: name registration for name %s \ +IP %s\n", namestr(question), inet_ntoa(from_ip) )); + + /* + * Deal with policy regarding 0x1d names. + */ + + if(question->name_type == 0x1d) + { + DEBUG(3,("wins_process_multihomed_name_registration_request: Ignoring request \ +to register name %s from IP %s.", namestr(question), inet_ntoa(p->ip) )); + send_wins_name_registration_response(0, ttl, p); + return; + } + + /* + * See if the name already exists. + */ + + namerec = find_name_on_subnet(subrec, question, FIND_ANY_NAME); + + /* + * Deal with the case where the name found was a dns entry. + * Remove it as we now have a NetBIOS client registering the + * name. + */ + + if((namerec != NULL) && ((namerec->source == DNS_NAME) || (namerec->source == DNSFAIL_NAME))) + { + DEBUG(5,("wins_process_multihomed_name_registration_request: Name (%s) in WINS was a dns lookup \ +- removing it.\n", namestr(question) )); + remove_name_from_namelist( subrec, namerec); + namerec = NULL; + } + + /* + * Reject if the name exists and is not a REGISTER_NAME. + * (ie. Don't allow any static names to be overwritten. + */ + + if((namerec != NULL) && (namerec->source != REGISTER_NAME)) + { + DEBUG(3,("wins_process_multihomed_name_registration_request: Attempt to register name %s. Name \ +already exists in WINS with source type %d.\n", namestr(question), namerec->source )); + send_wins_name_registration_response(RFS_ERR, 0, p); + return; + } + + /* + * Reject if the name exists and is a GROUP name. + */ + + if((namerec != NULL) && NAME_GROUP(namerec)) + { + DEBUG(3,("wins_process_multihomed_name_registration_request: Attempt to register name %s. Name \ +already exists in WINS as a GROUP name.\n", namestr(question) )); + send_wins_name_registration_response(RFS_ERR, 0, p); + return; + } + + /* + * From here on down we know that if the name exists in the WINS db it is + * a unique name, not a group name. + */ + + /* + * If the name exists and is one of our names then check the + * registering IP address. If it's not one of ours then automatically + * reject without doing the query - we know we will reject it. + */ + + if((namerec != NULL) && (is_myname(namerec->name.name)) ) + { + if(!ismyip(from_ip)) + { + DEBUG(3,("wins_process_multihomed_name_registration_request: Attempt to register name %s. Name \ +is one of our (WINS server) names. Denying registration.\n", namestr(question) )); + send_wins_name_registration_response(RFS_ERR, 0, p); + return; + } + else + { + /* + * It's one of our names and one of our IP's. Ensure the IP is in the record and + * update the ttl. + */ + if(!find_ip_in_name_record(namerec, from_ip)) + add_ip_to_name_record(namerec, from_ip); + update_name_ttl(namerec, ttl); + send_wins_name_registration_response(0, ttl, p); + return; + } + } + + /* + * If the name exists do a query to the owner + * to see if they still want the name. + */ + + if(namerec != NULL) + { + char ud[sizeof(struct userdata_struct) + sizeof(struct packet_struct *)]; + struct userdata_struct *userdata = (struct userdata_struct *)&ud; + + /* + * First send a WACK to the registering machine. + */ + + send_wins_wack_response(60, p); + + /* + * When the reply comes back we need the original packet. + * Lock this so it won't be freed and then put it into + * the userdata structure. + */ + + p->locked = True; + + userdata = (struct userdata_struct *)ud; + + userdata->copy_fn = NULL; + userdata->free_fn = NULL; + userdata->userdata_len = sizeof(struct packet_struct *); + memcpy(userdata->data, (char *)&p, sizeof(struct packet_struct *) ); + + /* + * As query_name uses the subnet broadcast address as the destination + * of the packet we temporarily change the subnet broadcast address to + * be the IP address of the requesting machine and send the packet. This + * is a *horrible* hack but the alternative is to add the destination + * address parameter to all query_name() calls. I hate this code :-). + */ + + subrec->bcast_ip = p->ip; + query_name( subrec, question->name, question->name_type, + wins_multihomed_register_query_success, + wins_multihomed_register_query_fail, + userdata); + subrec->bcast_ip = ipzero; + return; + } + + /* + * Name did not exist - add it. + */ + + add_name_to_subnet(subrec, question->name, question->name_type, + nb_flags, ttl, REGISTER_NAME, 1, &from_ip); + + send_wins_name_registration_response(0, ttl, p); +} + +/*********************************************************************** + Deal with the special name query for *<1b>. +***********************************************************************/ + +static void process_wins_dmb_query_request(struct subnet_record *subrec, + struct packet_struct *p) +{ + struct name_record *namerec = NULL; + char *prdata; + int num_ips; + + /* + * Go through all the names in the WINS db looking for those + * ending in <1b>. Use this to calculate the number of IP + * addresses we need to return. + */ + + num_ips = 0; + for(namerec = subrec->namelist; namerec; namerec = namerec->next) + { + if(namerec->name.name_type == 0x1b) + num_ips += namerec->num_ips; + } + + if(num_ips == 0) + { + /* + * There are no 0x1b names registered. Return name query fail. + */ + send_wins_name_query_response(NAM_ERR, p, NULL); + return; + } + + if((prdata = (char *)malloc( num_ips * 6 )) == NULL) + { + DEBUG(0,("process_wins_dmb_query_request: Malloc fail !.\n")); + return; + } + + /* + * Go through all the names again in the WINS db looking for those + * ending in <1b>. Add their IP addresses into the list we will + * return. + */ + + num_ips = 0; + for(namerec = subrec->namelist; namerec; namerec = namerec->next) + { + if(namerec->name.name_type == 0x1b) + { + int i; + for(i = 0; i < namerec->num_ips; i++) + { + set_nb_flags(&prdata[num_ips * 6],namerec->nb_flags); + putip((char *)&prdata[(num_ips * 6) + 2], &namerec->ip[i]); + num_ips++; + } + } + } + + /* + * Send back the reply containing the IP list. + */ + + reply_netbios_packet(p, /* Packet to reply to. */ + 0, /* Result code. */ + WINS_QUERY, /* nmbd type code. */ + NMB_NAME_QUERY_OPCODE, /* opcode. */ + lp_min_wins_ttl(), /* ttl. */ + prdata, /* data to send. */ + num_ips*6); /* data length. */ + + free(prdata); +} + +/**************************************************************************** +Send a WINS name query response. +**************************************************************************/ + +void send_wins_name_query_response(int rcode, struct packet_struct *p, + struct name_record *namerec) +{ + char rdata[6]; + char *prdata = rdata; + int reply_data_len = 0; + int ttl = 0; + int i = 0; + int j; + + bzero(rdata,6); + + if(rcode == 0) + { + int same_net_index; + + ttl = (namerec->death_time != PERMANENT_TTL) ? + namerec->death_time - p->timestamp : lp_max_wins_ttl(); + + /* Copy all known ip addresses into the return data. */ + /* Optimise for the common case of one IP address so + we don't need a malloc. */ + + if(namerec->num_ips == 1 ) + prdata = rdata; + else + { + if((prdata = (char *)malloc( namerec->num_ips * 6 )) == NULL) + { + DEBUG(0,("send_wins_name_query_response: malloc fail !\n")); + return; + } + + /* + * Look over the known IP addresses and see if one of them + * is on the same (local) net as the requesting IP address. If so then + * put that IP address into the packet as the first IP. + * We can only do this for local nets as they're the only + * ones we know the netmask for. + */ + + same_net_index = -1; + i = 0; + + if(is_local_net(p->ip)) + { + struct in_addr *n_mask = iface_nmask(p->ip); + + for( j = 0; j < namerec->num_ips; j++) + { + if(same_net( namerec->ip[j], p->ip, *n_mask)) + { + set_nb_flags(&prdata[0],namerec->nb_flags); + putip((char *)&prdata[2], &namerec->ip[j]); + same_net_index = j; + i = 1; + } + } + } + } + + for(; i < namerec->num_ips; i++) + { + if(i == same_net_index) + continue; + set_nb_flags(&prdata[i*6],namerec->nb_flags); + putip((char *)&prdata[2+(i*6)], &namerec->ip[i]); + } + reply_data_len = namerec->num_ips * 6; + + } + + reply_netbios_packet(p, /* Packet to reply to. */ + rcode, /* Result code. */ + WINS_QUERY, /* nmbd type code. */ + NMB_NAME_QUERY_OPCODE, /* opcode. */ + ttl, /* ttl. */ + prdata, /* data to send. */ + reply_data_len); /* data length. */ + + if((prdata != rdata) && (prdata != NULL)) + free(prdata); +} + +/*********************************************************************** + Deal with a name query. +***********************************************************************/ + +void wins_process_name_query_request(struct subnet_record *subrec, + struct packet_struct *p) +{ + struct nmb_packet *nmb = &p->packet.nmb; + struct nmb_name *question = &nmb->question.question_name; + struct name_record *namerec = NULL; + + DEBUG(3,("wins_process_name_query: name query for name %s from IP %s\n", + namestr(question), inet_ntoa(p->ip) )); + + /* + * Special name code. If the queried name is *<1b> then search + * the entire WINS database and return a list of all the IP addresses + * registered to any <1b> name. This is to allow domain master browsers + * to discover other domains that may not have a presence on their subnet. + */ + + if(strequal( question->name, "*") && (question->name_type == 0x1b)) + { + process_wins_dmb_query_request( subrec, p); + return; + } + + namerec = find_name_on_subnet(subrec, question, FIND_ANY_NAME); + + if(namerec != NULL) + { + /* + * If it's a DNSFAIL_NAME then reply name not found. + */ + + if(namerec->source == DNSFAIL_NAME) + { + DEBUG(3,("wins_process_name_query: name query for name %s returning DNS fail.\n", + namestr(question) )); + send_wins_name_query_response(NAM_ERR, p, namerec); + return; + } + + /* + * If the name has expired then reply name not found. + */ + + if((namerec->death_time != PERMANENT_TTL) && (namerec->death_time < p->timestamp)) + { + DEBUG(3,("wins_process_name_query: name query for name %s - name expired. Returning fail.\n", + namestr(question) )); + send_wins_name_query_response(NAM_ERR, p, namerec); + return; + } + + DEBUG(3,("wins_process_name_query: name query for name %s returning first IP %s.\n", + namestr(question), inet_ntoa(namerec->ip[0]) )); + + send_wins_name_query_response(0, p, namerec); + return; + } + + /* + * Name not found in WINS - try a dns query if it's a 0x20 name. + */ + + if(lp_dns_proxy() && (question->name_type == 0x20)) + { + + DEBUG(3,("wins_process_name_query: name query for name %s not found - doing dns lookup.\n", + namestr(question) )); + + queue_dns_query(p, question, &namerec); + return; + } + + /* + * Name not found - return error. + */ + + send_wins_name_query_response(NAM_ERR, p, NULL); +} + +/**************************************************************************** +Send a WINS name release response. +**************************************************************************/ + +static void send_wins_name_release_response(int rcode, struct packet_struct *p) +{ + struct nmb_packet *nmb = &p->packet.nmb; + char rdata[6]; + + memcpy(&rdata[0], &nmb->additional->rdata[0], 6); + + reply_netbios_packet(p, /* Packet to reply to. */ + rcode, /* Result code. */ + NMB_REL, /* nmbd type code. */ + NMB_NAME_RELEASE_OPCODE, /* opcode. */ + 0, /* ttl. */ + rdata, /* data to send. */ + 6); /* data length. */ +} + +/*********************************************************************** + Deal with a name release. +***********************************************************************/ + +void wins_process_name_release_request(struct subnet_record *subrec, + struct packet_struct *p) +{ + struct nmb_packet *nmb = &p->packet.nmb; + struct nmb_name *question = &nmb->question.question_name; + BOOL bcast = nmb->header.nm_flags.bcast; + uint16 nb_flags = get_nb_flags(nmb->additional->rdata); + struct name_record *namerec = NULL; + struct in_addr from_ip; + BOOL releasing_group_name = (nb_flags & NB_GROUP) ? True : False;; + + putip((char *)&from_ip,&nmb->additional->rdata[2]); + + if(bcast) + { + /* + * We should only get unicast name registration packets here. + * Anyone trying to register broadcast should not be going to a WINS + * server. Log an error here. + */ + + DEBUG(0,("wins_process_name_release_request: broadcast name registration request \ +received for name %s from IP %s on subnet %s. Error - should not be sent to WINS server\n", + namestr(question), inet_ntoa(from_ip), subrec->subnet_name)); + return; + } + + DEBUG(3,("wins_process_name_release_request: %s name release for name %s \ +IP %s\n", releasing_group_name ? "Group" : "Unique", namestr(question), inet_ntoa(from_ip) )); + + /* + * Deal with policy regarding 0x1d names. + */ + + if(!releasing_group_name && (question->name_type == 0x1d)) + { + DEBUG(3,("wins_process_name_release_request: Ignoring request \ +to release name %s from IP %s.", namestr(question), inet_ntoa(p->ip) )); + send_wins_name_release_response(0, p); + return; + } + + /* + * See if the name already exists. + */ + + namerec = find_name_on_subnet(subrec, question, FIND_ANY_NAME); + + if((namerec == NULL) || ((namerec != NULL) && (namerec->source != REGISTER_NAME)) ) + { + send_wins_name_release_response(NAM_ERR, p); + return; + } + + /* + * Check that the sending machine has permission to release this name. + * If it's a group name not ending in 0x1c then just say yes and let + * the group time out. + */ + + if(releasing_group_name && (question->name_type != 0x1c)) + { + send_wins_name_release_response(0, p); + return; + } + + /* + * Check that the releasing node is on the list of IP addresses + * for this name. Disallow the release if not. + */ + + if(!find_ip_in_name_record(namerec, from_ip)) + { + DEBUG(3,("wins_process_name_release_request: Refusing request to \ +release name %s as IP %s is not one of the known IP's for this name.\n", + namestr(question), inet_ntoa(from_ip) )); + send_wins_name_release_response(NAM_ERR, p); + return; + } + + /* + * Release the name and then remove the IP from the known list. + */ + + send_wins_name_release_response(0, p); + remove_ip_from_name_record(namerec, from_ip); + + /* + * Remove the name entirely if no IP addresses left. + */ + if (namerec->num_ips == 0) + remove_name_from_namelist(subrec, namerec); + +} + +/******************************************************************* + WINS time dependent processing. +******************************************************************/ + +void initiate_wins_processing(time_t t) +{ + static time_t lasttime = 0; + + if (!lasttime) + lasttime = t; + if (t - lasttime < 5) + return; + + if(!lp_we_are_a_wins_server()) + return; + + expire_names_on_subnet(wins_server_subnet, t); + + if(wins_server_subnet->namelist_changed) + wins_write_database(); + + wins_server_subnet->namelist_changed = False; +} + +/******************************************************************* + Write out the current WINS database. +******************************************************************/ + +void wins_write_database(void) +{ + struct name_record *namerec; + fstring fname, fnamenew; + + FILE *fp; + + if(!lp_we_are_a_wins_server()) + return; + + fstrcpy(fname,lp_lockdir()); + trim_string(fname,NULL,"/"); + strcat(fname,"/"); + strcat(fname,WINS_LIST); + fstrcpy(fnamenew,fname); + strcat(fnamenew,"."); + + if((fp = fopen(fnamenew,"w")) == NULL) + { + DEBUG(0,("wins_write_database: Can't open %s. Error was %s\n", fnamenew, strerror(errno))); + return; + } + + DEBUG(4,("wins_write_database: Dump of WINS name list.\n")); + + for (namerec = wins_server_subnet->namelist; namerec; namerec = namerec->next) + { + int i; + struct tm *tm; + + DEBUG(4,("%-19s ", namestr(&namerec->name) )); + + if(namerec->death_time != PERMANENT_TTL) + { + tm = LocalTime(&namerec->death_time); + DEBUG(4,("TTL = %s", asctime(tm) )); + } + else + DEBUG(4,("TTL = PERMANENT\t")); + + for (i = 0; i < namerec->num_ips; i++) + DEBUG(4,("%15s ", inet_ntoa(namerec->ip[i]) )); + DEBUG(4,("%2x\n", namerec->nb_flags )); + + if (namerec->source == REGISTER_NAME) + { + fprintf(fp, "%s#%02x %ld ", + namerec->name.name,namerec->name.name_type, /* Ignore scope. */ + namerec->death_time); + + for (i = 0; i < namerec->num_ips; i++) + fprintf(fp, "%s ", inet_ntoa(namerec->ip[i])); + fprintf(fp, "%2xR\n", namerec->nb_flags); + } + } + + fclose(fp); + unlink(fname); + chmod(fnamenew,0644); + rename(fnamenew,fname); +} -- cgit