From 8daa83a594a2e98f39d764422bfbdbc62c9efd44 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 19:20:00 +0200 Subject: Adding upstream version 2:4.20.0+dfsg. Signed-off-by: Daniel Baumann --- source3/nmbd/nmbd_winsserver.c | 2706 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2706 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 0000000..ecae447 --- /dev/null +++ b/source3/nmbd/nmbd_winsserver.c @@ -0,0 +1,2706 @@ +/* + Unix SMB/CIFS implementation. + NBT netbios routines and daemon - version 2 + + Copyright (C) Jeremy Allison 1994-2005 + + 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 3 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, see . + + Converted to store WINS data in a tdb. Dec 2005. JRA. +*/ + +#include "includes.h" +#include "system/filesys.h" +#include "nmbd/nmbd.h" +#include "util_tdb.h" + +#define WINS_LIST "wins.dat" +#define WINS_VERSION 1 +#define WINSDB_VERSION 1 + +/**************************************************************************** + We don't store the NetBIOS scope in the wins.tdb. We key off the (utf8) netbios + name (65 bytes with the last byte being the name type). +*****************************************************************************/ + +TDB_CONTEXT *wins_tdb; + +/**************************************************************************** + Delete all the temporary name records on the in-memory linked list. +*****************************************************************************/ + +static void wins_delete_all_tmp_in_memory_records(void) +{ + struct name_record *nr = NULL; + struct name_record *nrnext = NULL; + + /* Delete all temporary name records on the wins subnet linked list. */ + for( nr = wins_server_subnet->namelist; nr; nr = nrnext) { + nrnext = nr->next; + DLIST_REMOVE(wins_server_subnet->namelist, nr); + SAFE_FREE(nr->data.ip); + SAFE_FREE(nr); + } +} + +/**************************************************************************** + Delete all the temporary 1b name records on the in-memory linked list. +*****************************************************************************/ + +static void wins_delete_all_1b_in_memory_records(void) +{ + struct name_record *nr = NULL; + struct name_record *nrnext = NULL; + + /* Delete all temporary 1b name records on the wins subnet linked list. */ + for( nr = wins_server_subnet->namelist; nr; nr = nrnext) { + nrnext = nr->next; + if (nr->name.name_type == 0x1b) { + DLIST_REMOVE(wins_server_subnet->namelist, nr); + SAFE_FREE(nr->data.ip); + SAFE_FREE(nr); + } + } +} + +/**************************************************************************** + Convert a wins.tdb record to a struct name_record. Add in our lp_netbios_scope(). +*****************************************************************************/ + +static struct name_record *wins_record_to_name_record(TDB_DATA key, TDB_DATA data) +{ + struct name_record *namerec = NULL; + uint16_t nb_flags; + unsigned char nr_src; + uint32_t death_time, refresh_time; + uint32_t id_low, id_high; + uint32_t saddr; + uint32_t wins_flags; + uint32_t num_ips; + size_t len; + int i; + + if (data.dptr == NULL || data.dsize == 0) { + return NULL; + } + + /* Min size is "wbddddddd" + 1 ip address (4). */ + if (data.dsize < 2 + 1 + (7*4) + 4) { + return NULL; + } + + len = tdb_unpack(data.dptr, data.dsize, + "wbddddddd", + &nb_flags, + &nr_src, + &death_time, + &refresh_time, + &id_low, + &id_high, + &saddr, + &wins_flags, + &num_ips ); + + namerec = SMB_MALLOC_P(struct name_record); + if (!namerec) { + return NULL; + } + ZERO_STRUCTP(namerec); + + namerec->data.ip = SMB_MALLOC_ARRAY(struct in_addr, num_ips); + if (!namerec->data.ip) { + SAFE_FREE(namerec); + return NULL; + } + + namerec->subnet = wins_server_subnet; + push_ascii_nstring(namerec->name.name, (const char *)key.dptr); + namerec->name.name_type = key.dptr[sizeof(unstring)]; + /* Add the scope. */ + push_ascii(namerec->name.scope, lp_netbios_scope(), 64, STR_TERMINATE); + + /* We're using a byte-by-byte compare, so we must be sure that + * unused space doesn't have garbage in it. + */ + + for( i = strlen( namerec->name.name ); i < sizeof( namerec->name.name ); i++ ) { + namerec->name.name[i] = '\0'; + } + for( i = strlen( namerec->name.scope ); i < sizeof( namerec->name.scope ); i++ ) { + namerec->name.scope[i] = '\0'; + } + + namerec->data.nb_flags = nb_flags; + namerec->data.source = (enum name_source)nr_src; + namerec->data.death_time = (time_t)death_time; + namerec->data.refresh_time = (time_t)refresh_time; + namerec->data.id = id_low; + namerec->data.id |= ((uint64_t)id_high << 32); + namerec->data.wins_ip.s_addr = saddr; + namerec->data.wins_flags = wins_flags, + namerec->data.num_ips = num_ips; + + for (i = 0; i < num_ips; i++) { + namerec->data.ip[i].s_addr = IVAL(data.dptr, len + (i*4)); + } + + return namerec; +} + +/**************************************************************************** + Convert a struct name_record to a wins.tdb record. Ignore the scope. +*****************************************************************************/ + +static TDB_DATA name_record_to_wins_record(const struct name_record *namerec) +{ + TDB_DATA data; + size_t len = 0; + int i; + uint32_t id_low = (namerec->data.id & 0xFFFFFFFF); + uint32_t id_high = (namerec->data.id >> 32) & 0xFFFFFFFF; + + ZERO_STRUCT(data); + + len = (2 + 1 + (7*4)); /* "wbddddddd" */ + len += (namerec->data.num_ips * 4); + + data.dptr = (uint8_t *)SMB_MALLOC(len); + if (!data.dptr) { + return data; + } + data.dsize = len; + + len = tdb_pack(data.dptr, data.dsize, "wbddddddd", + namerec->data.nb_flags, + (unsigned char)namerec->data.source, + (uint32_t)namerec->data.death_time, + (uint32_t)namerec->data.refresh_time, + id_low, + id_high, + (uint32_t)namerec->data.wins_ip.s_addr, + (uint32_t)namerec->data.wins_flags, + (uint32_t)namerec->data.num_ips ); + + for (i = 0; i < namerec->data.num_ips; i++) { + SIVAL(data.dptr, len + (i*4), namerec->data.ip[i].s_addr); + } + + return data; +} + +/**************************************************************************** + Create key. Key is UNIX codepage namestring (usually utf8 64 byte len) with 1 byte type. +*****************************************************************************/ + +static TDB_DATA name_to_key(const struct nmb_name *nmbname) +{ + static char keydata[sizeof(unstring) + 1]; + TDB_DATA key; + + memset(keydata, '\0', sizeof(keydata)); + + pull_ascii_nstring(keydata, sizeof(unstring), nmbname->name); + (void)strupper_m(keydata); + keydata[sizeof(unstring)] = nmbname->name_type; + key.dptr = (uint8_t *)keydata; + key.dsize = sizeof(keydata); + + return key; +} + +/**************************************************************************** + Lookup a given name in the wins.tdb and create a temporary malloc'ed data struct + on the linked list. We will free this later in XXXX(). +*****************************************************************************/ + +struct name_record *find_name_on_wins_subnet(const struct nmb_name *nmbname, bool self_only) +{ + TDB_DATA data, key; + struct name_record *nr = NULL; + struct name_record *namerec = NULL; + + if (!wins_tdb) { + return NULL; + } + + key = name_to_key(nmbname); + data = tdb_fetch(wins_tdb, key); + + if (data.dsize == 0) { + return NULL; + } + + namerec = wins_record_to_name_record(key, data); + + /* done with the this */ + + SAFE_FREE( data.dptr ); + + if (!namerec) { + return NULL; + } + + /* Self names only - these include permanent names. */ + if( self_only && (namerec->data.source != SELF_NAME) && (namerec->data.source != PERMANENT_NAME) ) { + DEBUG( 9, ( "find_name_on_wins_subnet: self name %s NOT FOUND\n", nmb_namestr(nmbname) ) ); + SAFE_FREE(namerec->data.ip); + SAFE_FREE(namerec); + return NULL; + } + + /* Search for this name record on the list. Replace it if found. */ + + for( nr = wins_server_subnet->namelist; nr; nr = nr->next) { + if (memcmp(nmbname->name, nr->name.name, 16) == 0) { + /* Delete it. */ + DLIST_REMOVE(wins_server_subnet->namelist, nr); + SAFE_FREE(nr->data.ip); + SAFE_FREE(nr); + break; + } + } + + DLIST_ADD(wins_server_subnet->namelist, namerec); + return namerec; +} + +/**************************************************************************** + Overwrite or add a given name in the wins.tdb. +*****************************************************************************/ + +static bool store_or_replace_wins_namerec(const struct name_record *namerec, int tdb_flag) +{ + TDB_DATA key, data; + int ret; + + if (!wins_tdb) { + return False; + } + + key = name_to_key(&namerec->name); + data = name_record_to_wins_record(namerec); + + if (data.dptr == NULL) { + return False; + } + + ret = tdb_store(wins_tdb, key, data, tdb_flag); + + SAFE_FREE(data.dptr); + return (ret == 0) ? True : False; +} + +/**************************************************************************** + Overwrite a given name in the wins.tdb. +*****************************************************************************/ + +bool wins_store_changed_namerec(const struct name_record *namerec) +{ + return store_or_replace_wins_namerec(namerec, TDB_REPLACE); +} + +/**************************************************************************** + Primary interface into creating and overwriting records in the wins.tdb. +*****************************************************************************/ + +bool add_name_to_wins_subnet(const struct name_record *namerec) +{ + return store_or_replace_wins_namerec(namerec, TDB_INSERT); +} + +/**************************************************************************** + Delete a given name in the tdb and remove the temporary malloc'ed data struct + on the linked list. +*****************************************************************************/ + +bool remove_name_from_wins_namelist(struct name_record *namerec) +{ + TDB_DATA key; + int ret; + + if (!wins_tdb) { + return False; + } + + key = name_to_key(&namerec->name); + ret = tdb_delete(wins_tdb, key); + + DLIST_REMOVE(wins_server_subnet->namelist, namerec); + + /* namerec must be freed by the caller */ + + return (ret == 0) ? True : False; +} + +/**************************************************************************** + Dump out the complete namelist. +*****************************************************************************/ + +static int traverse_fn(TDB_CONTEXT *tdb, TDB_DATA kbuf, TDB_DATA dbuf, void *state) +{ + struct name_record *namerec = NULL; + FILE *fp = (FILE *)state; + + if (kbuf.dsize != sizeof(unstring) + 1) { + return 0; + } + + namerec = wins_record_to_name_record(kbuf, dbuf); + if (!namerec) { + return 0; + } + + dump_name_record(namerec, fp); + + SAFE_FREE(namerec->data.ip); + SAFE_FREE(namerec); + return 0; +} + +void dump_wins_subnet_namelist(FILE *fp) +{ + tdb_traverse(wins_tdb, traverse_fn, (void *)fp); +} + +/**************************************************************************** + Change the wins owner address in the record. +*****************************************************************************/ + +static void update_wins_owner(struct name_record *namerec, struct in_addr wins_ip) +{ + namerec->data.wins_ip=wins_ip; +} + +/**************************************************************************** + Create the wins flags based on the nb flags and the input value. +*****************************************************************************/ + +static void update_wins_flag(struct name_record *namerec, int flags) +{ + namerec->data.wins_flags=0x0; + + /* if it's a group, it can be a normal or a special one */ + if (namerec->data.nb_flags & NB_GROUP) { + if (namerec->name.name_type==0x1C) { + namerec->data.wins_flags|=WINS_SGROUP; + } else { + if (namerec->data.num_ips>1) { + namerec->data.wins_flags|=WINS_SGROUP; + } else { + namerec->data.wins_flags|=WINS_NGROUP; + } + } + } else { + /* can be unique or multi-homed */ + if (namerec->data.num_ips>1) { + namerec->data.wins_flags|=WINS_MHOMED; + } else { + namerec->data.wins_flags|=WINS_UNIQUE; + } + } + + /* the node type are the same bits */ + namerec->data.wins_flags|=namerec->data.nb_flags&NB_NODETYPEMASK; + + /* the static bit is elsewhere */ + if (namerec->data.death_time == PERMANENT_TTL) { + namerec->data.wins_flags|=WINS_STATIC; + } + + /* and add the given bits */ + namerec->data.wins_flags|=flags; + + DEBUG(8,("update_wins_flag: nbflags: 0x%x, ttl: %d, flags: 0x%x, winsflags: 0x%x\n", + namerec->data.nb_flags, (int)namerec->data.death_time, flags, namerec->data.wins_flags)); +} + +/**************************************************************************** + Return the general ID value and increase it if requested. +*****************************************************************************/ + +static void get_global_id_and_update(uint64_t *current_id, bool update) +{ + /* + * it's kept as a static here, to prevent people from messing + * with the value directly + */ + + static uint64_t general_id = 1; + + DEBUG(5,("get_global_id_and_update: updating version ID: %d\n", (int)general_id)); + + *current_id = general_id; + + if (update) { + general_id++; + } +} + +/**************************************************************************** + Possibly call the WINS hook external program when a WINS change is made. + Also stores the changed record back in the wins_tdb. +*****************************************************************************/ + +static void wins_hook(const char *operation, struct name_record *namerec, int ttl) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + char *command = NULL; + char *cmd = lp_wins_hook(talloc_tos(), lp_sub); + char *p, *namestr; + int i; + TALLOC_CTX *ctx = talloc_tos(); + + wins_store_changed_namerec(namerec); + + if (!cmd || !*cmd) { + return; + } + + for (p=namerec->name.name; *p; p++) { + if (!(isalnum((int)*p) || strchr_m("._-",*p))) { + DEBUG(3,("not calling wins hook for invalid name %s\n", nmb_namestr(&namerec->name))); + return; + } + } + + /* Use the name without the nametype (and scope) appended */ + + namestr = nmb_namestr(&namerec->name); + if ((p = strchr(namestr, '<'))) { + *p = 0; + } + + command = talloc_asprintf(ctx, + "%s %s %s %02x %d", + cmd, + operation, + namestr, + namerec->name.name_type, + ttl); + if (!command) { + return; + } + + for (i=0;idata.num_ips;i++) { + command = talloc_asprintf_append(command, + " %s", + inet_ntoa(namerec->data.ip[i])); + if (!command) { + return; + } + } + + DEBUG(3,("calling wins hook for %s\n", nmb_namestr(&namerec->name))); + smbrun(command, NULL, NULL); + TALLOC_FREE(command); +} + +/**************************************************************************** +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) +{ + time_t time_now = time(NULL); + FILE *fp; + char line[1024]; + char *db_path; + char *list_path; + + if(!lp_we_are_a_wins_server()) { + return True; + } + + db_path = state_path(talloc_tos(), "wins.tdb"); + if (db_path == NULL) { + return false; + } + + /* Open the wins.tdb. */ + wins_tdb = tdb_open_log(db_path, 0, TDB_DEFAULT|TDB_CLEAR_IF_FIRST|TDB_INCOMPATIBLE_HASH, + O_CREAT|O_RDWR, 0600); + TALLOC_FREE(db_path); + if (!wins_tdb) { + DEBUG(0,("initialise_wins: failed to open wins.tdb. Error was %s\n", + strerror(errno) )); + return False; + } + + tdb_store_int32(wins_tdb, "WINSDB_VERSION", WINSDB_VERSION); + + add_samba_names_to_subnet(wins_server_subnet); + + list_path = state_path(talloc_tos(), WINS_LIST); + if (list_path == NULL) { + tdb_close(wins_tdb); + return false; + } + + fp = fopen(list_path, "r"); + TALLOC_FREE(list_path); + if (fp == NULL) { + DEBUG(2,("initialise_wins: Can't open wins database file %s. Error was %s\n", + WINS_LIST, strerror(errno) )); + return True; + } + + while (!feof(fp)) { + char *name_str = NULL; + char *ip_str = NULL; + char *ttl_str = NULL, *nb_flags_str = NULL; + unsigned int num_ips; + char *name = NULL; + struct in_addr *ip_list = NULL; + int type = 0; + int nb_flags; + int ttl; + const char *ptr; + char *p = NULL; + bool got_token; + bool was_ip; + int i; + unsigned int hash; + int version; + TALLOC_CTX *frame = NULL; + + /* Read a line from the wins.dat file. Strips whitespace + from the beginning and end of the line. */ + if (!fgets_slash(NULL, line, sizeof(line), fp)) { + continue; + } + + if (*line == '#') { + continue; + } + + if (strncmp(line,"VERSION ", 8) == 0) { + if (sscanf(line,"VERSION %d %u", &version, &hash) != 2 || + version != WINS_VERSION) { + DEBUG(0,("Discarding invalid wins.dat file [%s]\n",line)); + fclose(fp); + return True; + } + 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. + */ + + frame = talloc_stackframe(); + if (!next_token_talloc(frame,&ptr,&name_str,NULL)) { + DEBUG(0,("initialise_wins: Failed to parse name when parsing line %s\n", line )); + TALLOC_FREE(frame); + continue; + } + + if (!next_token_talloc(frame,&ptr,&ttl_str,NULL)) { + DEBUG(0,("initialise_wins: Failed to parse time to live when parsing line %s\n", line )); + TALLOC_FREE(frame); + continue; + } + + /* + * Determine the number of IP addresses per line. + */ + num_ips = 0; + do { + got_token = next_token_talloc(frame,&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 )); + TALLOC_FREE(frame); + continue; + } + + if(!got_token) { + DEBUG(0,("initialise_wins: Missing nb_flags when parsing line %s\n", line )); + TALLOC_FREE(frame); + continue; + } + + /* Allocate the space for the ip_list. */ + if((ip_list = SMB_MALLOC_ARRAY( struct in_addr, num_ips)) == NULL) { + DEBUG(0,("initialise_wins: Malloc fail !\n")); + fclose(fp); + TALLOC_FREE(frame); + return False; + } + + /* Reset and re-parse the line. */ + ptr = line; + next_token_talloc(frame,&ptr,&name_str,NULL); + next_token_talloc(frame,&ptr,&ttl_str,NULL); + for(i = 0; i < num_ips; i++) { + next_token_talloc(frame,&ptr, &ip_str, NULL); + ip_list[i] = interpret_addr2(ip_str); + } + next_token_talloc(frame,&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)); + SAFE_FREE(ip_list); + TALLOC_FREE(frame); + 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 */ + 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,"%x",&nb_flags); + sscanf(ttl_str,"%d",&ttl); + + /* add all entries that have 60 seconds or more to live */ + if ((ttl - 60) > time_now || ttl == PERMANENT_TTL) { + if(ttl != PERMANENT_TTL) { + ttl -= time_now; + } + + DEBUG( 4, ("initialise_wins: add name: %s#%02x ttl = %d first IP %s flags = %2x\n", + name, type, ttl, inet_ntoa(ip_list[0]), nb_flags)); + + (void)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 = %d first IP %s flags = %2x\n", + name, type, ttl, inet_ntoa(ip_list[0]), nb_flags)); + } + + TALLOC_FREE(frame); + SAFE_FREE(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_t 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; + struct in_addr our_fake_ip; + + our_fake_ip = interpret_addr2("0.0.0.0"); + 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. + */ + if( DEBUGLVL( 0 ) ) { + dbgtext( "wins_process_name_refresh_request: " ); + dbgtext( "Broadcast name refresh request received " ); + dbgtext( "for name %s ", nmb_namestr(question) ); + dbgtext( "from IP %s ", inet_ntoa(from_ip) ); + dbgtext( "on subnet %s. ", subrec->subnet_name ); + dbgtext( "Error - Broadcasts should not be sent " ); + dbgtext( "to a WINS server\n" ); + } + return; + } + + if( DEBUGLVL( 3 ) ) { + dbgtext( "wins_process_name_refresh_request: " ); + dbgtext( "Name refresh for name %s IP %s\n", + nmb_namestr(question), inet_ntoa(from_ip) ); + } + + /* + * See if the name already exists. + * If not, handle it as a name registration and return. + */ + namerec = find_name_on_subnet(subrec, question, FIND_ANY_NAME); + + /* + * If this is a refresh request and the name doesn't exist then + * treat it like a registration request. This allows us to recover + * from errors (tridge) + */ + if(namerec == NULL) { + if( DEBUGLVL( 3 ) ) { + dbgtext( "wins_process_name_refresh_request: " ); + dbgtext( "Name refresh for name %s ", + nmb_namestr( question ) ); + dbgtext( "and the name does not exist. Treating " ); + dbgtext( "as registration.\n" ); + } + wins_process_name_registration_request(subrec,p); + return; + } + + /* + * if the name is present but not active, simply remove it + * and treat the refresh request as a registration & return. + */ + if (namerec != NULL && !WINS_STATE_ACTIVE(namerec)) { + if( DEBUGLVL( 5 ) ) { + dbgtext( "wins_process_name_refresh_request: " ); + dbgtext( "Name (%s) in WINS ", nmb_namestr(question) ); + dbgtext( "was not active - removing it.\n" ); + } + remove_name_from_namelist( subrec, namerec ); + namerec = NULL; + wins_process_name_registration_request( subrec, p ); + return; + } + + /* + * Check that the group bits for the refreshing name and the + * name in our database match. If not, refuse the refresh. + * [crh: Why RFS_ERR instead of ACT_ERR? Is this what MS does?] + */ + if( (namerec != NULL) && + ( (group && !NAME_GROUP(namerec)) + || (!group && NAME_GROUP(namerec)) ) ) { + if( DEBUGLVL( 3 ) ) { + dbgtext( "wins_process_name_refresh_request: " ); + dbgtext( "Name %s ", nmb_namestr(question) ); + dbgtext( "group bit = %s does not match ", + group ? "True" : "False" ); + dbgtext( "group bit in WINS for this name.\n" ); + } + 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); + + /* + * if the record is a replica: + * we take ownership and update the version ID. + */ + if (!ip_equal_v4(namerec->data.wins_ip, our_fake_ip)) { + update_wins_owner(namerec, our_fake_ip); + get_global_id_and_update(&namerec->data.id, True); + } + + send_wins_name_registration_response(0, ttl, p); + wins_hook("refresh", namerec, ttl); + return; + } else if((group && (question->name_type == 0x1c))) { + /* + * Added by crh for bug #1079. + * Fix from Bert Driehuis + */ + if( DEBUGLVL( 3 ) ) { + dbgtext( "wins_process_name_refresh_request: " ); + dbgtext( "Name refresh for name %s, ", + nmb_namestr(question) ); + dbgtext( "but IP address %s ", inet_ntoa(from_ip) ); + dbgtext( "is not yet associated with " ); + dbgtext( "that name. Treating as registration.\n" ); + } + wins_process_name_registration_request(subrec,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); + wins_hook("refresh", 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. + */ + if( DEBUGLVL( 3 ) ) { + dbgtext( "wins_process_name_refresh_request: " ); + dbgtext( "Name refresh for name %s with IP %s ", + nmb_namestr(question), inet_ntoa(from_ip) ); + dbgtext( "and is IP is not known to the name.\n" ); + } + 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 requester. +************************************************************************/ + +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), nmb_namestr(question_name) )); + + send_wins_name_registration_response(ACT_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 name_record *namerec = NULL; + + memcpy((char *)&orig_reg_packet, userdata->data, sizeof(struct packet_struct *)); + + /* + * 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 arbitrary + * 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->data.source == REGISTER_NAME) && + ip_equal_v4(rrec->packet->ip, *namerec->data.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", + nmb_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) +{ + unstring name; + struct nmb_packet *nmb = &p->packet.nmb; + struct nmb_name *question = &nmb->question.question_name; + bool bcast = nmb->header.nm_flags.bcast; + uint16_t 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; + struct in_addr our_fake_ip; + + our_fake_ip = interpret_addr2("0.0.0.0"); + 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", + nmb_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", nmb_namestr(question), inet_ntoa(from_ip) )); + + /* + * See if the name already exists. + */ + + namerec = find_name_on_subnet(subrec, question, FIND_ANY_NAME); + + /* + * if the record exists but NOT in active state, + * consider it dead. + */ + if ( (namerec != NULL) && !WINS_STATE_ACTIVE(namerec)) { + DEBUG(5,("wins_process_name_registration_request: Name (%s) in WINS was \ +not active - removing it.\n", nmb_namestr(question) )); + remove_name_from_namelist( subrec, namerec ); + namerec = NULL; + } + + /* + * 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->data.source == DNS_NAME) || (namerec->data.source == DNSFAIL_NAME) ) ) { + DEBUG(5,("wins_process_name_registration_request: Name (%s) in WINS was \ +a dns lookup - removing it.\n", nmb_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->data.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", + nmb_namestr(question), namerec->data.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.\n", nmb_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), nmb_namestr(question) )); + + /* + * Check the ip address is not already in the group. + */ + + if(!find_ip_in_name_record(namerec, from_ip)) { + /* + * Need to emulate the behaviour of Windows, as + * described in: + * http://lists.samba.org/archive/samba-technical/2001-October/016236.html + * (is there an MS reference for this + * somewhere?) because if the 1c list gets over + * 86 entries, the reply packet is too big + * (rdata>576 bytes) so no reply is sent. + * + * Keep only the "latest" 25 records, while + * ensuring that the PDC (0x1b) is never removed + * We do this by removing the first entry that + * isn't the 1b entry for the same name, + * on the grounds that insertion is at the end + * of the list, so the oldest entries are at + * the start. + * + */ + while(namerec->data.num_ips>=25) { + struct name_record *name1brec = NULL; + + /* We only do this for 1c types. */ + if (namerec->name.name_type != 0x1c) { + break; + } + DEBUG(3,("wins_process_name_registration_request: " + "More than 25 IPs already in " + "the list. Looking for a 1b " + "record\n")); + + /* Ensure we have all the active 1b + * names on the list. */ + wins_delete_all_1b_in_memory_records(); + fetch_all_active_wins_1b_names(); + + /* Per the above, find the 1b record, + and then remove the first IP that isn't the same */ + for(name1brec = subrec->namelist; + name1brec; + name1brec = name1brec->next ) { + if( WINS_STATE_ACTIVE(name1brec) && + name1brec->name.name_type == 0x1b) { + DEBUG(3,("wins_process_name_registration_request: " + "Found the #1b record " + "with ip %s\n", + inet_ntoa(name1brec->data.ip[0]))); + break; + } + } + if(!name1brec) { + DEBUG(3,("wins_process_name_registration_request: " + "Didn't find a #1b name record. " + "Removing the first available " + "entry %s\n", + inet_ntoa(namerec->data.ip[0]))); + remove_ip_from_name_record(namerec, namerec->data.ip[0]); + wins_hook("delete", namerec, 0); + } else { + int i; + for(i=0; idata.num_ips; i++) { + /* The name1brec should only have + * the single IP address in it, + * so we only check against the first one*/ + if(!ip_equal_v4( namerec->data.ip[i], name1brec->data.ip[0])) { + /* The i'th entry isn't the 1b address; delete it */ + DEBUG(3,("wins_process_name_registration_request: " + "Entry at %d is not the #1b address. " + "About to remove it\n", + i)); + remove_ip_from_name_record(namerec, namerec->data.ip[i]); + wins_hook("delete", namerec, 0); + break; + } + } + } + } + /* The list is guaranteed to be < 25 entries now + * - safe to add a new one */ + add_ip_to_name_record(namerec, from_ip); + /* we need to update the record for replication */ + get_global_id_and_update(&namerec->data.id, True); + + /* + * if the record is a replica, we must change + * the wins owner to us to make the replication updates + * it on the other wins servers. + * And when the partner will receive this record, + * it will update its own record. + */ + + update_wins_owner(namerec, our_fake_ip); + } + update_name_ttl(namerec, ttl); + wins_hook("refresh", 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. + * + * explanation: groups have a higher priority than unique names. + */ + + DEBUG(3,("wins_process_name_registration_request: Attempt to register name %s. Name \ +already exists in WINS as a GROUP name.\n", nmb_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 ) { + pull_ascii_nstring(name, sizeof(name), namerec->name.name); + if( is_myname(name) ) { + if(!ismyip_v4(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", nmb_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); + wins_hook("refresh", namerec, ttl); + send_wins_name_registration_response(0, ttl, p); + return; + } + } + } else { + name[0] = '\0'; + } + + /* + * If the name exists and it is a unique registration and the registering IP + * is the same as the (single) already registered IP then just update the ttl. + * + * But not if the record is an active replica. IF it's a replica, it means it can be + * the same client which has moved and not yet expired. So we don't update + * the ttl in this case and go beyond to do a WACK and query the old client + */ + + if( !registering_group_name + && (namerec != NULL) + && (namerec->data.num_ips == 1) + && ip_equal_v4( namerec->data.ip[0], from_ip ) + && ip_equal_v4(namerec->data.wins_ip, our_fake_ip) ) { + update_name_ttl( namerec, ttl ); + wins_hook("refresh", 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 ) { + long *ud[(sizeof(struct userdata_struct) + sizeof(struct packet_struct *))/sizeof(long *) + 1]; + 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 *) ); + + /* + * Use the new call to send a query directly to an IP address. + * This sends the query directly to the IP address, and ensures + * the recursion desired flag is not set (you were right Luke :-). + * This function should *only* be called from the WINS server + * code. JRA. + */ + + pull_ascii_nstring(name, sizeof(name), question->name); + query_name_from_wins_server( *namerec->data.ip, + name, + question->name_type, + wins_register_query_success, + wins_register_query_fail, + userdata ); + return; + } + + /* + * Name did not exist - add it. + */ + + pull_ascii_nstring(name, sizeof(name), question->name); + add_name_to_subnet( subrec, name, question->name_type, + nb_flags, ttl, REGISTER_NAME, 1, &from_ip); + + if ((namerec = find_name_on_subnet(subrec, question, FIND_ANY_NAME))) { + get_global_id_and_update(&namerec->data.id, True); + update_wins_owner(namerec, our_fake_ip); + update_wins_flag(namerec, WINS_ACTIVE); + wins_hook("add", namerec, ttl); + } + + 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; + struct in_addr our_fake_ip; + + our_fake_ip = interpret_addr2("0.0.0.0"); + 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 arbitrary + * 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->data.source != REGISTER_NAME) || !WINS_STATE_ACTIVE(namerec) ) { + DEBUG(3,("wins_multihomed_register_query_success: name %s is not in the correct state to add \ +a subsequent IP address.\n", nmb_namestr(question_name) )); + send_wins_name_registration_response(RFS_ERR, 0, orig_reg_packet); + + orig_reg_packet->locked = False; + free_packet(orig_reg_packet); + + return; + } + + if(!find_ip_in_name_record(namerec, from_ip)) { + add_ip_to_name_record(namerec, from_ip); + } + + get_global_id_and_update(&namerec->data.id, True); + update_wins_owner(namerec, our_fake_ip); + update_wins_flag(namerec, WINS_ACTIVE); + update_name_ttl(namerec, ttl); + wins_hook("add", namerec, ttl); + send_wins_name_registration_response(0, ttl, 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. +************************************************************************/ + +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), nmb_namestr(question_name) )); + send_wins_name_registration_response(RFS_ERR, 0, orig_reg_packet); + + orig_reg_packet->locked = False; + free_packet(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_t 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; + struct in_addr our_fake_ip; + unstring qname; + + our_fake_ip = interpret_addr2("0.0.0.0"); + 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", + nmb_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. Error - group names should not be multihomed.\n", + nmb_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", nmb_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.\n", nmb_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); + + /* + * if the record exists but NOT in active state, + * consider it dead. + */ + + if ((namerec != NULL) && !WINS_STATE_ACTIVE(namerec)) { + DEBUG(5,("wins_process_multihomed_name_registration_request: Name (%s) in WINS was not active - removing it.\n", nmb_namestr(question))); + remove_name_from_namelist(subrec, namerec); + namerec = NULL; + } + + /* + * 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->data.source == DNS_NAME) || (namerec->data.source == DNSFAIL_NAME) ) ) { + DEBUG(5,("wins_process_multihomed_name_registration_request: Name (%s) in WINS was a dns lookup \ +- removing it.\n", nmb_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->data.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", + nmb_namestr(question), namerec->data.source )); + send_wins_name_registration_response(RFS_ERR, 0, p); + return; + } + + /* + * Reject if the name exists and is a GROUP name and is active. + */ + + if((namerec != NULL) && NAME_GROUP(namerec) && WINS_STATE_ACTIVE(namerec)) { + DEBUG(3,("wins_process_multihomed_name_registration_request: Attempt to register name %s. Name \ +already exists in WINS as a GROUP name.\n", nmb_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_v4(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", nmb_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. Update the version ID to force replication. + */ + update_name_ttl(namerec, ttl); + + if(!find_ip_in_name_record(namerec, from_ip)) { + get_global_id_and_update(&namerec->data.id, True); + update_wins_owner(namerec, our_fake_ip); + update_wins_flag(namerec, WINS_ACTIVE); + + add_ip_to_name_record(namerec, from_ip); + } + + wins_hook("refresh", namerec, ttl); + send_wins_name_registration_response(0, ttl, p); + return; + } + } + + /* + * If the name exists and is active, check if the IP address is already registered + * to that name. If so then update the ttl and reply success. + */ + + if((namerec != NULL) && find_ip_in_name_record(namerec, from_ip) && WINS_STATE_ACTIVE(namerec)) { + update_name_ttl(namerec, ttl); + + /* + * If it's a replica, we need to become the wins owner + * to force the replication + */ + if (!ip_equal_v4(namerec->data.wins_ip, our_fake_ip)) { + get_global_id_and_update(&namerec->data.id, True); + update_wins_owner(namerec, our_fake_ip); + update_wins_flag(namerec, WINS_ACTIVE); + } + + wins_hook("refresh", 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) { + long *ud[(sizeof(struct userdata_struct) + sizeof(struct packet_struct *))/sizeof(long *) + 1]; + 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 *) ); + + /* + * Use the new call to send a query directly to an IP address. + * This sends the query directly to the IP address, and ensures + * the recursion desired flag is not set (you were right Luke :-). + * This function should *only* be called from the WINS server + * code. JRA. + * + * Note that this packet is sent to the current owner of the name, + * not the person who sent the packet + */ + + pull_ascii_nstring( qname, sizeof(qname), question->name); + query_name_from_wins_server( namerec->data.ip[0], + qname, + question->name_type, + wins_multihomed_register_query_success, + wins_multihomed_register_query_fail, + userdata ); + + return; + } + + /* + * Name did not exist - add it. + */ + + pull_ascii_nstring( qname, sizeof(qname), question->name); + add_name_to_subnet( subrec, qname, question->name_type, + nb_flags, ttl, REGISTER_NAME, 1, &from_ip); + + if ((namerec = find_name_on_subnet(subrec, question, FIND_ANY_NAME))) { + get_global_id_and_update(&namerec->data.id, True); + update_wins_owner(namerec, our_fake_ip); + update_wins_flag(namerec, WINS_ACTIVE); + wins_hook("add", namerec, ttl); + } + + send_wins_name_registration_response(0, ttl, p); +} + +/*********************************************************************** + Fetch all *<1b> names from the WINS db and store on the namelist. +***********************************************************************/ + +static int fetch_1b_traverse_fn(TDB_CONTEXT *tdb, TDB_DATA kbuf, TDB_DATA dbuf, void *state) +{ + struct name_record *namerec = NULL; + + if (kbuf.dsize != sizeof(unstring) + 1) { + return 0; + } + + /* Filter out all non-1b names. */ + if (kbuf.dptr[sizeof(unstring)] != 0x1b) { + return 0; + } + + namerec = wins_record_to_name_record(kbuf, dbuf); + if (!namerec) { + return 0; + } + + DLIST_ADD(wins_server_subnet->namelist, namerec); + return 0; +} + +void fetch_all_active_wins_1b_names(void) +{ + tdb_traverse(wins_tdb, fetch_1b_traverse_fn, NULL); +} + +/*********************************************************************** + 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 ACTIVE 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; + + /* First, clear the in memory list - we're going to re-populate + it with the tdb_traversal in fetch_all_active_wins_1b_names. */ + + wins_delete_all_tmp_in_memory_records(); + + fetch_all_active_wins_1b_names(); + + for( namerec = subrec->namelist; namerec; namerec = namerec->next ) { + if( WINS_STATE_ACTIVE(namerec) && namerec->name.name_type == 0x1b) { + num_ips += namerec->data.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 *)SMB_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( WINS_STATE_ACTIVE(namerec) && namerec->name.name_type == 0x1b) { + int i; + for(i = 0; i < namerec->data.num_ips; i++) { + set_nb_flags(&prdata[num_ips * 6],namerec->data.nb_flags); + putip((char *)&prdata[(num_ips * 6) + 2], &namerec->data.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. */ + + SAFE_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; + + memset(rdata,'\0',6); + + if(rcode == 0) { + + int ip_count; + + ttl = (namerec->data.death_time != PERMANENT_TTL) ? namerec->data.death_time - p->timestamp : lp_max_wins_ttl(); + + /* The netbios reply packet data section is limited to 576 bytes. In theory + * this should give us space for 96 addresses, but in practice, 86 appears + * to be the max (don't know why). If we send any more than that, + * reply_netbios_packet will fail to send a reply to avoid a memcpy buffer + * overflow. Keep the count to 85 and it will be ok */ + ip_count=namerec->data.num_ips; + if(ip_count>85) { + ip_count=85; + } + + /* 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( ip_count == 1 ) { + prdata = rdata; + } else { + if((prdata = (char *)SMB_MALLOC( ip_count * 6 )) == NULL) { + DEBUG(0,("send_wins_name_query_response: malloc fail !\n")); + return; + } + } + + for(i = 0; i < ip_count; i++) { + set_nb_flags(&prdata[i*6],namerec->data.nb_flags); + putip((char *)&prdata[2+(i*6)], &namerec->data.ip[i]); + } + + sort_query_replies(prdata, i, p->ip); + reply_data_len = ip_count * 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) { + SAFE_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; + unstring qname; + + DEBUG(3,("wins_process_name_query: name query for name %s from IP %s\n", + nmb_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. + */ + + pull_ascii_nstring(qname, sizeof(qname), question->name); + if(strequal( qname, "*") && (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 the name is not anymore in active state then reply not found. + * it's fair even if we keep it in the cache for days. + */ + if (!WINS_STATE_ACTIVE(namerec)) { + DEBUG(3,("wins_process_name_query: name query for name %s - name expired. Returning fail.\n", + nmb_namestr(question) )); + send_wins_name_query_response(NAM_ERR, p, namerec); + return; + } + + /* + * If it's a DNSFAIL_NAME then reply name not found. + */ + + if( namerec->data.source == DNSFAIL_NAME ) { + DEBUG(3,("wins_process_name_query: name query for name %s returning DNS fail.\n", + nmb_namestr(question) )); + send_wins_name_query_response(NAM_ERR, p, namerec); + return; + } + + /* + * If the name has expired then reply name not found. + */ + + if( (namerec->data.death_time != PERMANENT_TTL) && (namerec->data.death_time < p->timestamp) ) { + DEBUG(3,("wins_process_name_query: name query for name %s - name expired. Returning fail.\n", + nmb_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", + nmb_namestr(question), inet_ntoa(namerec->data.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_wins_dns_proxy() && ((question->name_type == 0x20) || question->name_type == 0)) { + DEBUG(3,("wins_process_name_query: name query for name %s not found - doing dns lookup.\n", + nmb_namestr(question) )); + + queue_dns_query(p, question); + 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_t 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", + nmb_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", nmb_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.\n", nmb_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->data.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", + nmb_namestr(question), inet_ntoa(from_ip) )); + send_wins_name_release_response(NAM_ERR, p); + return; + } + + /* + * Check if the record is active. IF it's already released + * or tombstoned, refuse the release. + */ + + if (!WINS_STATE_ACTIVE(namerec)) { + DEBUG(3,("wins_process_name_release_request: Refusing request to \ +release name %s as this record is not active anymore.\n", nmb_namestr(question) )); + send_wins_name_release_response(NAM_ERR, p); + return; + } + + /* + * Check if the record is a 0x1c group + * and has more then one ip + * remove only this address. + */ + + if(releasing_group_name && (question->name_type == 0x1c) && (namerec->data.num_ips > 1)) { + remove_ip_from_name_record(namerec, from_ip); + DEBUG(3,("wins_process_name_release_request: Remove IP %s from NAME: %s\n", + inet_ntoa(from_ip),nmb_namestr(question))); + wins_hook("delete", namerec, 0); + send_wins_name_release_response(0, p); + return; + } + + /* + * Send a release response. + * Flag the name as released and update the ttl + */ + + namerec->data.wins_flags |= WINS_RELEASED; + update_name_ttl(namerec, EXTINCTION_INTERVAL); + + wins_hook("delete", namerec, 0); + send_wins_name_release_response(0, p); +} + +/******************************************************************* + WINS time dependent processing. +******************************************************************/ + +static int wins_processing_traverse_fn(TDB_CONTEXT *tdb, TDB_DATA kbuf, TDB_DATA dbuf, void *state) +{ + time_t t = *(time_t *)state; + bool store_record = False; + struct name_record *namerec = NULL; + struct in_addr our_fake_ip; + + our_fake_ip = interpret_addr2("0.0.0.0"); + if (kbuf.dsize != sizeof(unstring) + 1) { + return 0; + } + + namerec = wins_record_to_name_record(kbuf, dbuf); + if (!namerec) { + return 0; + } + + if( (namerec->data.death_time != PERMANENT_TTL) && (namerec->data.death_time < t) ) { + if( namerec->data.source == SELF_NAME ) { + DEBUG( 3, ( "wins_processing_traverse_fn: Subnet %s not expiring SELF name %s\n", + wins_server_subnet->subnet_name, nmb_namestr(&namerec->name) ) ); + namerec->data.death_time += 300; + store_record = True; + goto done; + } else if (namerec->data.source == DNS_NAME || namerec->data.source == DNSFAIL_NAME) { + DEBUG(3,("wins_processing_traverse_fn: deleting timed out DNS name %s\n", + nmb_namestr(&namerec->name))); + remove_name_from_wins_namelist(namerec ); + goto done; + } + + /* handle records, samba is the wins owner */ + if (ip_equal_v4(namerec->data.wins_ip, our_fake_ip)) { + switch (namerec->data.wins_flags & WINS_STATE_MASK) { + case WINS_ACTIVE: + namerec->data.wins_flags&=~WINS_STATE_MASK; + namerec->data.wins_flags|=WINS_RELEASED; + namerec->data.death_time = t + EXTINCTION_INTERVAL; + DEBUG(3,("wins_processing_traverse_fn: expiring %s\n", + nmb_namestr(&namerec->name))); + store_record = True; + goto done; + case WINS_RELEASED: + namerec->data.wins_flags&=~WINS_STATE_MASK; + namerec->data.wins_flags|=WINS_TOMBSTONED; + namerec->data.death_time = t + EXTINCTION_TIMEOUT; + get_global_id_and_update(&namerec->data.id, True); + DEBUG(3,("wins_processing_traverse_fn: tombstoning %s\n", + nmb_namestr(&namerec->name))); + store_record = True; + goto done; + case WINS_TOMBSTONED: + DEBUG(3,("wins_processing_traverse_fn: deleting %s\n", + nmb_namestr(&namerec->name))); + remove_name_from_wins_namelist(namerec ); + goto done; + } + } else { + switch (namerec->data.wins_flags & WINS_STATE_MASK) { + case WINS_ACTIVE: + /* that's not as MS says it should be */ + namerec->data.wins_flags&=~WINS_STATE_MASK; + namerec->data.wins_flags|=WINS_TOMBSTONED; + namerec->data.death_time = t + EXTINCTION_TIMEOUT; + DEBUG(3,("wins_processing_traverse_fn: tombstoning %s\n", + nmb_namestr(&namerec->name))); + store_record = True; + goto done; + case WINS_TOMBSTONED: + DEBUG(3,("wins_processing_traverse_fn: deleting %s\n", + nmb_namestr(&namerec->name))); + remove_name_from_wins_namelist(namerec ); + goto done; + case WINS_RELEASED: + DEBUG(0,("wins_processing_traverse_fn: %s is in released state and\ +we are not the wins owner !\n", nmb_namestr(&namerec->name))); + goto done; + } + } + } + + done: + + if (store_record) { + wins_store_changed_namerec(namerec); + } + + SAFE_FREE(namerec->data.ip); + SAFE_FREE(namerec); + + return 0; +} + +/******************************************************************* + Time dependent wins processing. +******************************************************************/ + +void initiate_wins_processing(time_t t) +{ + static time_t lasttime = 0; + + if (!lasttime) { + lasttime = t; + } + if (t - lasttime < 20) { + return; + } + + if(!lp_we_are_a_wins_server()) { + lasttime = t; + return; + } + + tdb_traverse(wins_tdb, wins_processing_traverse_fn, &t); + + wins_delete_all_tmp_in_memory_records(); + + wins_write_database(t, True); + + lasttime = t; +} + +/******************************************************************* + Write out one record. +******************************************************************/ + +void wins_write_name_record(struct name_record *namerec, FILE *fp) +{ + int i; + struct tm *tm; + + DEBUGADD(4,("%-19s ", nmb_namestr(&namerec->name) )); + + if( namerec->data.death_time != PERMANENT_TTL ) { + char *ts, *nl; + + tm = localtime(&namerec->data.death_time); + if (!tm) { + return; + } + ts = asctime(tm); + if (!ts) { + return; + } + nl = strrchr( ts, '\n' ); + if( NULL != nl ) { + *nl = '\0'; + } + DEBUGADD(4,("TTL = %s ", ts )); + } else { + DEBUGADD(4,("TTL = PERMANENT ")); + } + + for (i = 0; i < namerec->data.num_ips; i++) { + DEBUGADD(4,("%15s ", inet_ntoa(namerec->data.ip[i]) )); + } + DEBUGADD(4,("%2x\n", namerec->data.nb_flags )); + + if( namerec->data.source == REGISTER_NAME ) { + unstring name; + pull_ascii_nstring(name, sizeof(name), namerec->name.name); + fprintf(fp, "\"%s#%02x\" %d ", name, + namerec->name.name_type, /* Ignore scope. */ + (int)namerec->data.death_time); + + for (i = 0; i < namerec->data.num_ips; i++) + fprintf(fp, "%s ", inet_ntoa(namerec->data.ip[i])); + fprintf(fp, "%2xR\n", namerec->data.nb_flags); + } +} + +/******************************************************************* + Write out the current WINS database. +******************************************************************/ + +static int wins_writedb_traverse_fn(TDB_CONTEXT *tdb, TDB_DATA kbuf, TDB_DATA dbuf, void *state) +{ + struct name_record *namerec = NULL; + FILE *fp = (FILE *)state; + + if (kbuf.dsize != sizeof(unstring) + 1) { + return 0; + } + + namerec = wins_record_to_name_record(kbuf, dbuf); + if (!namerec) { + return 0; + } + + wins_write_name_record(namerec, fp); + + SAFE_FREE(namerec->data.ip); + SAFE_FREE(namerec); + return 0; +} + + +void wins_write_database(time_t t, bool background) +{ + static time_t last_write_time = 0; + char *fname = NULL; + char *fnamenew = NULL; + + int fd; + FILE *fp; + + if (background) { + if (!last_write_time) { + last_write_time = t; + } + if (t - last_write_time < 120) { + return; + } + + } + + if(!lp_we_are_a_wins_server()) { + return; + } + + /* We will do the writing in a child process to ensure that the parent doesn't block while this is done */ + if (background) { + CatchChild(); + if (fork()) { + return; + } + if (tdb_reopen(wins_tdb)) { + DEBUG(0,("wins_write_database: tdb_reopen failed. Error was %s\n", + strerror(errno))); + _exit(0); + return; + } + } + + if (!(fname = state_path(talloc_tos(), WINS_LIST))) { + goto err_exit; + } + /* This is safe as the 0 length means "don't expand". */ + all_string_sub(fname,"//", "/", 0); + + if (asprintf(&fnamenew, "%s.%u", fname, (unsigned int)getpid()) < 0) { + goto err_exit; + } + + fd = open(fnamenew, O_WRONLY|O_CREAT, 0644); + if (fd == -1) { + DBG_ERR("Can't open %s: %s\n", fnamenew, strerror(errno)); + goto err_exit; + } + + fp = fdopen(fd, "w"); + if (fp == NULL) { + DBG_ERR("fdopen failed: %s\n", strerror(errno)); + close(fd); + goto err_exit; + } + fd = -1; + + DEBUG(4,("wins_write_database: Dump of WINS name list.\n")); + + fprintf(fp,"VERSION %d %u\n", WINS_VERSION, 0); + + tdb_traverse(wins_tdb, wins_writedb_traverse_fn, fp); + + fclose(fp); + chmod(fnamenew,0644); + unlink(fname); + rename(fnamenew,fname); + + err_exit: + + SAFE_FREE(fnamenew); + TALLOC_FREE(fname); + + if (background) { + _exit(0); + } +} + +#if 0 + Until winsrepl is done. +/**************************************************************************** + Process a internal Samba message receiving a wins record. +***************************************************************************/ + +void nmbd_wins_new_entry(struct messaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + WINS_RECORD *record; + struct name_record *namerec = NULL; + struct name_record *new_namerec = NULL; + struct nmb_name question; + bool overwrite=False; + struct in_addr our_fake_ip; + int i; + + our_fake_ip = interpret_addr2("0.0.0.0"); + if (buf==NULL) { + return; + } + + /* Record should use UNIX codepage. Ensure this is so in the wrepld code. JRA. */ + record=(WINS_RECORD *)buf; + + make_nmb_name(&question, record->name, record->type); + + namerec = find_name_on_subnet(wins_server_subnet, &question, FIND_ANY_NAME); + + /* record doesn't exist, add it */ + if (namerec == NULL) { + DEBUG(3,("nmbd_wins_new_entry: adding new replicated record: %s<%02x> for wins server: %s\n", + record->name, record->type, inet_ntoa(record->wins_ip))); + + new_namerec=add_name_to_subnet( wins_server_subnet, + record->name, + record->type, + record->nb_flags, + EXTINCTION_INTERVAL, + REGISTER_NAME, + record->num_ips, + record->ip); + + if (new_namerec!=NULL) { + update_wins_owner(new_namerec, record->wins_ip); + update_wins_flag(new_namerec, record->wins_flags); + new_namerec->data.id=record->id; + + wins_server_subnet->namelist_changed = True; + } + } + + /* check if we have a conflict */ + if (namerec != NULL) { + /* both records are UNIQUE */ + if (namerec->data.wins_flags&WINS_UNIQUE && record->wins_flags&WINS_UNIQUE) { + + /* the database record is a replica */ + if (!ip_equal_v4(namerec->data.wins_ip, our_fake_ip)) { + if (namerec->data.wins_flags&WINS_ACTIVE && record->wins_flags&WINS_TOMBSTONED) { + if (ip_equal_v4(namerec->data.wins_ip, record->wins_ip)) + overwrite=True; + } else + overwrite=True; + } else { + /* we are the wins owner of the database record */ + /* the 2 records have the same IP address */ + if (ip_equal_v4(namerec->data.ip[0], record->ip[0])) { + if (namerec->data.wins_flags&WINS_ACTIVE && record->wins_flags&WINS_TOMBSTONED) + get_global_id_and_update(&namerec->data.id, True); + else + overwrite=True; + + } else { + /* the 2 records have different IP address */ + if (namerec->data.wins_flags&WINS_ACTIVE) { + if (record->wins_flags&WINS_TOMBSTONED) + get_global_id_and_update(&namerec->data.id, True); + if (record->wins_flags&WINS_ACTIVE) + /* send conflict challenge to the replica node */ + ; + } else + overwrite=True; + } + + } + } + + /* the replica is a standard group */ + if (record->wins_flags&WINS_NGROUP || record->wins_flags&WINS_SGROUP) { + /* if the database record is unique and active force a name release */ + if (namerec->data.wins_flags&WINS_UNIQUE) + /* send a release name to the unique node */ + ; + overwrite=True; + + } + + /* the replica is a special group */ + if (record->wins_flags&WINS_SGROUP && namerec->data.wins_flags&WINS_SGROUP) { + if (namerec->data.wins_flags&WINS_ACTIVE) { + for (i=0; inum_ips; i++) + if(!find_ip_in_name_record(namerec, record->ip[i])) + add_ip_to_name_record(namerec, record->ip[i]); + } else { + overwrite=True; + } + } + + /* the replica is a multihomed host */ + + /* I'm giving up on multi homed. Too much complex to understand */ + + if (record->wins_flags&WINS_MHOMED) { + if (! (namerec->data.wins_flags&WINS_ACTIVE)) { + if ( !(namerec->data.wins_flags&WINS_RELEASED) && !(namerec->data.wins_flags&WINS_NGROUP)) + overwrite=True; + } + else { + if (ip_equal_v4(record->wins_ip, namerec->data.wins_ip)) + overwrite=True; + + if (ip_equal_v4(namerec->data.wins_ip, our_fake_ip)) + if (namerec->data.wins_flags&WINS_UNIQUE) + get_global_id_and_update(&namerec->data.id, True); + + } + + if (record->wins_flags&WINS_ACTIVE && namerec->data.wins_flags&WINS_ACTIVE) + if (namerec->data.wins_flags&WINS_UNIQUE || + namerec->data.wins_flags&WINS_MHOMED) + if (ip_equal_v4(record->wins_ip, namerec->data.wins_ip)) + overwrite=True; + + } + + if (overwrite == False) + DEBUG(3, ("nmbd_wins_new_entry: conflict in adding record: %s<%02x> from wins server: %s\n", + record->name, record->type, inet_ntoa(record->wins_ip))); + else { + DEBUG(3, ("nmbd_wins_new_entry: replacing record: %s<%02x> from wins server: %s\n", + record->name, record->type, inet_ntoa(record->wins_ip))); + + /* remove the old record and add a new one */ + remove_name_from_namelist( wins_server_subnet, namerec ); + new_namerec=add_name_to_subnet( wins_server_subnet, record->name, record->type, record->nb_flags, + EXTINCTION_INTERVAL, REGISTER_NAME, record->num_ips, record->ip); + if (new_namerec!=NULL) { + update_wins_owner(new_namerec, record->wins_ip); + update_wins_flag(new_namerec, record->wins_flags); + new_namerec->data.id=record->id; + + wins_server_subnet->namelist_changed = True; + } + + wins_server_subnet->namelist_changed = True; + } + + } +} +#endif -- cgit v1.2.3