diff options
Diffstat (limited to 'source4/nmbd')
28 files changed, 14204 insertions, 0 deletions
diff --git a/source4/nmbd/.cvsignore b/source4/nmbd/.cvsignore new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/source4/nmbd/.cvsignore diff --git a/source4/nmbd/asyncdns.c b/source4/nmbd/asyncdns.c new file mode 100644 index 0000000000..c86ee69a09 --- /dev/null +++ b/source4/nmbd/asyncdns.c @@ -0,0 +1,349 @@ +/* + Unix SMB/CIFS implementation. + a async DNS handler + Copyright (C) Andrew Tridgell 1997-1998 + + 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" + +/*************************************************************************** + Add a DNS result to the name cache. +****************************************************************************/ + +static struct name_record *add_dns_result(struct nmb_name *question, struct in_addr addr) +{ + int name_type = question->name_type; + char *qname = question->name; + + + if (!addr.s_addr) { + /* add the fail to WINS cache of names. give it 1 hour in the cache */ + DEBUG(3,("add_dns_result: Negative DNS answer for %s\n", qname)); + (void)add_name_to_subnet( wins_server_subnet, qname, name_type, + NB_ACTIVE, 60*60, DNSFAIL_NAME, 1, &addr ); + return( NULL ); + } + + /* add it to our WINS cache of names. give it 2 hours in the cache */ + DEBUG(3,("add_dns_result: DNS gave answer for %s of %s\n", qname, inet_ntoa(addr))); + + return( add_name_to_subnet( wins_server_subnet, qname, name_type, + NB_ACTIVE, 2*60*60, DNS_NAME, 1, &addr ) ); +} + + + +#ifndef SYNC_DNS + +static int fd_in = -1, fd_out = -1; +static pid_t child_pid = -1; +static int in_dns; + +/* this is the structure that is passed between the parent and child */ +struct query_record { + struct nmb_name name; + struct in_addr result; +}; + +/* a queue of pending requests waiting to be sent to the DNS child */ +static struct packet_struct *dns_queue; + +/* the packet currently being processed by the dns child */ +static struct packet_struct *dns_current; + + +/*************************************************************************** + return the fd used to gather async dns replies. This is added to the select + loop + ****************************************************************************/ +int asyncdns_fd(void) +{ + return fd_in; +} + +/*************************************************************************** + handle DNS queries arriving from the parent + ****************************************************************************/ +static void asyncdns_process(void) +{ + struct query_record r; + fstring qname; + + DEBUGLEVEL = -1; + + while (1) { + if (read_data(fd_in, (char *)&r, sizeof(r)) != sizeof(r)) + break; + + fstrcpy(qname, r.name.name); + + r.result.s_addr = interpret_addr(qname); + + if (write_data(fd_out, (char *)&r, sizeof(r)) != sizeof(r)) + break; + } + + _exit(0); +} + +/**************************************************************************** ** + catch a sigterm (in the child process - the parent has a different handler + see nmbd.c for details). + We need a separate term handler here so we don't release any + names that our parent is going to release, or overwrite a + WINS db that our parent is going to write. + **************************************************************************** */ + +static void sig_term(int sig) +{ + _exit(0); +} + +/*************************************************************************** + Called by the parent process when it receives a SIGTERM - also kills the + child so we don't get child async dns processes lying around, causing trouble. + ****************************************************************************/ + +void kill_async_dns_child(void) +{ + if (child_pid > 0) { + kill(child_pid, SIGTERM); + child_pid = -1; + } +} + +/*************************************************************************** + create a child process to handle DNS lookups + ****************************************************************************/ +void start_async_dns(void) +{ + int fd1[2], fd2[2]; + + CatchChild(); + + if (pipe(fd1) || pipe(fd2)) { + DEBUG(0,("can't create asyncdns pipes\n")); + return; + } + + child_pid = sys_fork(); + + if (child_pid) { + fd_in = fd1[0]; + fd_out = fd2[1]; + close(fd1[1]); + close(fd2[0]); + DEBUG(0,("started asyncdns process %d\n", (int)child_pid)); + return; + } + + fd_in = fd2[0]; + fd_out = fd1[1]; + + CatchSignal(SIGUSR2, SIG_IGN); + CatchSignal(SIGUSR1, SIG_IGN); + CatchSignal(SIGHUP, SIG_IGN); + CatchSignal(SIGTERM, SIGNAL_CAST sig_term ); + + asyncdns_process(); +} + + +/*************************************************************************** +check if a particular name is already being queried + ****************************************************************************/ +static BOOL query_current(struct query_record *r) +{ + return dns_current && + nmb_name_equal(&r->name, + &dns_current->packet.nmb.question.question_name); +} + + +/*************************************************************************** + write a query to the child process + ****************************************************************************/ +static BOOL write_child(struct packet_struct *p) +{ + struct query_record r; + + r.name = p->packet.nmb.question.question_name; + + return write_data(fd_out, (char *)&r, sizeof(r)) == sizeof(r); +} + +/*************************************************************************** + check the DNS queue + ****************************************************************************/ +void run_dns_queue(void) +{ + struct query_record r; + struct packet_struct *p, *p2; + struct name_record *namerec; + int size; + + if (fd_in == -1) + return; + + /* Allow SIGTERM to kill us. */ + BlockSignals(False, SIGTERM); + + if (!process_exists(child_pid)) { + close(fd_in); + start_async_dns(); + } + + if ((size=read_data(fd_in, (char *)&r, sizeof(r))) != sizeof(r)) { + if (size) { + DEBUG(0,("Incomplete DNS answer from child!\n")); + fd_in = -1; + } + BlockSignals(True, SIGTERM); + return; + } + + BlockSignals(True, SIGTERM); + + namerec = add_dns_result(&r.name, r.result); + + if (dns_current) { + if (query_current(&r)) { + DEBUG(3,("DNS calling send_wins_name_query_response\n")); + in_dns = 1; + if(namerec == NULL) + send_wins_name_query_response(NAM_ERR, dns_current, NULL); + else + send_wins_name_query_response(0,dns_current,namerec); + in_dns = 0; + } + + dns_current->locked = False; + free_packet(dns_current); + dns_current = NULL; + } + + /* loop over the whole dns queue looking for entries that + match the result we just got */ + for (p = dns_queue; p;) { + struct nmb_packet *nmb = &p->packet.nmb; + struct nmb_name *question = &nmb->question.question_name; + + if (nmb_name_equal(question, &r.name)) { + DEBUG(3,("DNS calling send_wins_name_query_response\n")); + in_dns = 1; + if(namerec == NULL) + send_wins_name_query_response(NAM_ERR, p, NULL); + else + send_wins_name_query_response(0,p,namerec); + in_dns = 0; + p->locked = False; + + if (p->prev) + p->prev->next = p->next; + else + dns_queue = p->next; + if (p->next) + p->next->prev = p->prev; + p2 = p->next; + free_packet(p); + p = p2; + } else { + p = p->next; + } + } + + if (dns_queue) { + dns_current = dns_queue; + dns_queue = dns_queue->next; + if (dns_queue) dns_queue->prev = NULL; + dns_current->next = NULL; + + if (!write_child(dns_current)) { + DEBUG(3,("failed to send DNS query to child!\n")); + return; + } + } + +} + +/*************************************************************************** +queue a DNS query + ****************************************************************************/ +BOOL queue_dns_query(struct packet_struct *p,struct nmb_name *question, + struct name_record **n) +{ + if (in_dns || fd_in == -1) + return False; + + if (!dns_current) { + if (!write_child(p)) { + DEBUG(3,("failed to send DNS query to child!\n")); + return False; + } + dns_current = p; + p->locked = True; + } else { + p->locked = True; + p->next = dns_queue; + p->prev = NULL; + if (p->next) + p->next->prev = p; + dns_queue = p; + } + + DEBUG(3,("added DNS query for %s\n", nmb_namestr(question))); + return True; +} + +#else + + +/*************************************************************************** + we use this when we can't do async DNS lookups + ****************************************************************************/ +BOOL queue_dns_query(struct packet_struct *p,struct nmb_name *question, + struct name_record **n) +{ + char *qname = question->name; + struct in_addr dns_ip; + + DEBUG(3,("DNS search for %s - ", nmb_namestr(question))); + + /* Unblock TERM signal so we can be killed in DNS lookup. */ + BlockSignals(False, SIGTERM); + + dns_ip.s_addr = interpret_addr(qname); + + /* Re-block TERM signal. */ + BlockSignals(True, SIGTERM); + + *n = add_dns_result(question, dns_ip); + if(*n == NULL) + send_wins_name_query_response(NAM_ERR, p, NULL); + else + send_wins_name_query_response(0, p, *n); + return False; +} + +/*************************************************************************** + With sync dns there is no child to kill on SIGTERM. + ****************************************************************************/ +void kill_async_dns_child(void) +{ + return; +} +#endif diff --git a/source4/nmbd/nmbd.c b/source4/nmbd/nmbd.c new file mode 100644 index 0000000000..2b7d8033a2 --- /dev/null +++ b/source4/nmbd/nmbd.c @@ -0,0 +1,778 @@ +/* + Unix SMB/CIFS implementation. + NBT netbios routines and daemon - version 2 + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Jeremy Allison 1997-2002 + Copyright (C) Jelmer Vernooij 2002 (Conversion to popt) + + 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" + +int ClientNMB = -1; +int ClientDGRAM = -1; +int global_nmb_port = -1; + +extern BOOL global_in_nmbd; + +/* are we running as a daemon ? */ +static BOOL is_daemon = False; + +/* fork or run in foreground ? */ +static BOOL Fork = True; + +/* log to standard output ? */ +static BOOL log_stdout = False; + +/* have we found LanMan clients yet? */ +BOOL found_lm_clients = False; + +/* what server type are we currently */ + +time_t StartupTime = 0; + +/**************************************************************************** ** + Handle a SIGTERM in band. + **************************************************************************** */ + +static void terminate(void) +{ + DEBUG(0,("Got SIGTERM: going down...\n")); + + /* Write out wins.dat file if samba is a WINS server */ + wins_write_database(False); + + /* Remove all SELF registered names from WINS */ + release_wins_names(); + + /* Announce all server entries as 0 time-to-live, 0 type. */ + announce_my_servers_removed(); + + /* If there was an async dns child - kill it. */ + kill_async_dns_child(); + + exit(0); +} + +/**************************************************************************** ** + Handle a SHUTDOWN message from smbcontrol. + **************************************************************************** */ + +static void nmbd_terminate(int msg_type, pid_t src, void *buf, size_t len) +{ + terminate(); +} + +/**************************************************************************** ** + Catch a SIGTERM signal. + **************************************************************************** */ + +static SIG_ATOMIC_T got_sig_term; + +static void sig_term(int sig) +{ + got_sig_term = 1; + sys_select_signal(); +} + +/**************************************************************************** ** + Catch a SIGHUP signal. + **************************************************************************** */ + +static SIG_ATOMIC_T reload_after_sighup; + +static void sig_hup(int sig) +{ + reload_after_sighup = 1; + sys_select_signal(); +} + +/******************************************************************* + Print out all talloc memory info. +********************************************************************/ + +void return_all_talloc_info(int msg_type, pid_t src_pid, void *buf, size_t len) +{ + TALLOC_CTX *ctx = talloc_init("info context"); + char *info = NULL; + + if (!ctx) + return; + + info = talloc_describe_all(ctx); + if (info) + DEBUG(10,(info)); + message_send_pid(src_pid, MSG_TALLOC_USAGE, info, info ? strlen(info) + 1 : 0, True); + talloc_destroy(ctx); +} + +#if DUMP_CORE +/**************************************************************************** ** + Prepare to dump a core file - carefully! + **************************************************************************** */ + +static BOOL dump_core(void) +{ + char *p; + pstring dname; + pstrcpy( dname, lp_logfile() ); + if ((p=strrchr_m(dname,'/'))) + *p=0; + pstrcat( dname, "/corefiles" ); + mkdir( dname, 0700 ); + sys_chown( dname, getuid(), getgid() ); + chmod( dname, 0700 ); + if ( chdir(dname) ) + return( False ); + umask( ~(0700) ); + +#ifdef HAVE_GETRLIMIT +#ifdef RLIMIT_CORE + { + struct rlimit rlp; + getrlimit( RLIMIT_CORE, &rlp ); + rlp.rlim_cur = MAX( 4*1024*1024, rlp.rlim_cur ); + setrlimit( RLIMIT_CORE, &rlp ); + getrlimit( RLIMIT_CORE, &rlp ); + DEBUG( 3, ( "Core limits now %d %d\n", (int)rlp.rlim_cur, (int)rlp.rlim_max ) ); + } +#endif +#endif + + + DEBUG(0,("Dumping core in %s\n",dname)); + abort(); + return( True ); +} +#endif + +/**************************************************************************** ** + Possibly continue after a fault. + **************************************************************************** */ + +static void fault_continue(void) +{ +#if DUMP_CORE + dump_core(); +#endif +} + +/**************************************************************************** ** + Expire old names from the namelist and server list. + **************************************************************************** */ + +static void expire_names_and_servers(time_t t) +{ + static time_t lastrun = 0; + + if ( !lastrun ) + lastrun = t; + if ( t < (lastrun + 5) ) + return; + lastrun = t; + + /* + * Expire any timed out names on all the broadcast + * subnets and those registered with the WINS server. + * (nmbd_namelistdb.c) + */ + + expire_names(t); + + /* + * Go through all the broadcast subnets and for each + * workgroup known on that subnet remove any expired + * server names. If a workgroup has an empty serverlist + * and has itself timed out then remove the workgroup. + * (nmbd_workgroupdb.c) + */ + + expire_workgroups_and_servers(t); +} + +/************************************************************************** ** + Reload the list of network interfaces. + ************************************************************************** */ + +static BOOL reload_interfaces(time_t t) +{ + static time_t lastt; + int n; + struct subnet_record *subrec; + extern BOOL rescan_listen_set; + extern struct in_addr loopback_ip; + + if (t && ((t - lastt) < NMBD_INTERFACES_RELOAD)) return False; + lastt = t; + + if (!interfaces_changed()) return False; + + /* the list of probed interfaces has changed, we may need to add/remove + some subnets */ + load_interfaces(); + + /* find any interfaces that need adding */ + for (n=iface_count() - 1; n >= 0; n--) { + struct interface *iface = get_interface(n); + + /* + * We don't want to add a loopback interface, in case + * someone has added 127.0.0.1 for smbd, nmbd needs to + * ignore it here. JRA. + */ + + if (ip_equal(iface->ip, loopback_ip)) { + DEBUG(2,("reload_interfaces: Ignoring loopback interface %s\n", inet_ntoa(iface->ip))); + continue; + } + + for (subrec=subnetlist; subrec; subrec=subrec->next) { + if (ip_equal(iface->ip, subrec->myip) && + ip_equal(iface->nmask, subrec->mask_ip)) break; + } + + if (!subrec) { + /* it wasn't found! add it */ + DEBUG(2,("Found new interface %s\n", + inet_ntoa(iface->ip))); + subrec = make_normal_subnet(iface); + if (subrec) register_my_workgroup_one_subnet(subrec); + } + } + + /* find any interfaces that need deleting */ + for (subrec=subnetlist; subrec; subrec=subrec->next) { + for (n=iface_count() - 1; n >= 0; n--) { + struct interface *iface = get_interface(n); + if (ip_equal(iface->ip, subrec->myip) && + ip_equal(iface->nmask, subrec->mask_ip)) break; + } + if (n == -1) { + /* oops, an interface has disapeared. This is + tricky, we don't dare actually free the + interface as it could be being used, so + instead we just wear the memory leak and + remove it from the list of interfaces without + freeing it */ + DEBUG(2,("Deleting dead interface %s\n", + inet_ntoa(subrec->myip))); + close_subnet(subrec); + } + } + + rescan_listen_set = True; + + /* We need to shutdown if there are no subnets... */ + if (FIRST_SUBNET == NULL) { + DEBUG(0,("reload_interfaces: No subnets to listen to. Shutting down...\n")); + return True; + } + return False; +} + +/**************************************************************************** ** + Reload the services file. + **************************************************************************** */ + +static BOOL reload_nmbd_services(BOOL test) +{ + BOOL ret; + + set_remote_machine_name("nmbd"); + + if ( lp_loaded() ) { + pstring fname; + pstrcpy( fname,lp_configfile()); + if (file_exist(fname,NULL) && !strcsequal(fname,dyn_CONFIGFILE)) { + pstrcpy(dyn_CONFIGFILE,fname); + test = False; + } + } + + if ( test && !lp_file_list_changed() ) + return(True); + + ret = lp_load( dyn_CONFIGFILE, True , False, False); + + /* perhaps the config filename is now set */ + if ( !test ) { + DEBUG( 3, ( "services not loaded\n" ) ); + reload_nmbd_services( True ); + } + + return(ret); +} + +/**************************************************************************** ** + The main select loop. + **************************************************************************** */ + +static void process(void) +{ + BOOL run_election; + + while( True ) { + time_t t = time(NULL); + + /* Check for internal messages */ + + message_dispatch(); + + /* + * Check all broadcast subnets to see if + * we need to run an election on any of them. + * (nmbd_elections.c) + */ + + run_election = check_elections(); + + /* + * Read incoming UDP packets. + * (nmbd_packets.c) + */ + + if(listen_for_packets(run_election)) + return; + + /* + * Handle termination inband. + */ + + if (got_sig_term) { + got_sig_term = 0; + terminate(); + } + + /* + * Process all incoming packets + * read above. This calls the success and + * failure functions registered when response + * packets arrrive, and also deals with request + * packets from other sources. + * (nmbd_packets.c) + */ + + run_packet_queue(); + + /* + * Run any elections - initiate becoming + * a local master browser if we have won. + * (nmbd_elections.c) + */ + + run_elections(t); + + /* + * Send out any broadcast announcements + * of our server names. This also announces + * the workgroup name if we are a local + * master browser. + * (nmbd_sendannounce.c) + */ + + announce_my_server_names(t); + + /* + * Send out any LanMan broadcast announcements + * of our server names. + * (nmbd_sendannounce.c) + */ + + announce_my_lm_server_names(t); + + /* + * If we are a local master browser, periodically + * announce ourselves to the domain master browser. + * This also deals with syncronising the domain master + * browser server lists with ourselves as a local + * master browser. + * (nmbd_sendannounce.c) + */ + + announce_myself_to_domain_master_browser(t); + + /* + * Fullfill any remote announce requests. + * (nmbd_sendannounce.c) + */ + + announce_remote(t); + + /* + * Fullfill any remote browse sync announce requests. + * (nmbd_sendannounce.c) + */ + + browse_sync_remote(t); + + /* + * Scan the broadcast subnets, and WINS client + * namelists and refresh any that need refreshing. + * (nmbd_mynames.c) + */ + + refresh_my_names(t); + + /* + * Scan the subnet namelists and server lists and + * expire thos that have timed out. + * (nmbd.c) + */ + + expire_names_and_servers(t); + + /* + * Write out a snapshot of our current browse list into + * the browse.dat file. This is used by smbd to service + * incoming NetServerEnum calls - used to synchronise + * browse lists over subnets. + * (nmbd_serverlistdb.c) + */ + + write_browse_list(t, False); + + /* + * If we are a domain master browser, we have a list of + * local master browsers we should synchronise browse + * lists with (these are added by an incoming local + * master browser announcement packet). Expire any of + * these that are no longer current, and pull the server + * lists from each of these known local master browsers. + * (nmbd_browsesync.c) + */ + + dmb_expire_and_sync_browser_lists(t); + + /* + * Check that there is a local master browser for our + * workgroup for all our broadcast subnets. If one + * is not found, start an election (which we ourselves + * may or may not participate in, depending on the + * setting of the 'local master' parameter. + * (nmbd_elections.c) + */ + + check_master_browser_exists(t); + + /* + * If we are configured as a logon server, attempt to + * register the special NetBIOS names to become such + * (WORKGROUP<1c> name) on all broadcast subnets and + * with the WINS server (if used). If we are configured + * to become a domain master browser, attempt to register + * the special NetBIOS name (WORKGROUP<1b> name) to + * become such. + * (nmbd_become_dmb.c) + */ + + add_domain_names(t); + + /* + * If we are a WINS server, do any timer dependent + * processing required. + * (nmbd_winsserver.c) + */ + + initiate_wins_processing(t); + + /* + * If we are a domain master browser, attempt to contact the + * WINS server to get a list of all known WORKGROUPS/DOMAINS. + * This will only work to a Samba WINS server. + * (nmbd_browsesync.c) + */ + + if (lp_enhanced_browsing()) + collect_all_workgroup_names_from_wins_server(t); + + /* + * Go through the response record queue and time out or re-transmit + * and expired entries. + * (nmbd_packets.c) + */ + + retransmit_or_expire_response_records(t); + + /* + * check to see if any remote browse sync child processes have completed + */ + + sync_check_completion(); + + /* + * regularly sync with any other DMBs we know about + */ + + if (lp_enhanced_browsing()) + sync_all_dmbs(t); + + /* + * clear the unexpected packet queue + */ + + clear_unexpected(t); + + /* + * Reload the services file if we got a sighup. + */ + + if(reload_after_sighup) { + DEBUG( 0, ( "Got SIGHUP dumping debug info.\n" ) ); + write_browse_list( 0, True ); + dump_all_namelists(); + reload_nmbd_services( True ); + reopen_logs(); + if(reload_interfaces(0)) + return; + reload_after_sighup = 0; + } + + /* check for new network interfaces */ + + if(reload_interfaces(t)) + return; + + /* free up temp memory */ + lp_talloc_free(); + } +} + +/**************************************************************************** ** + Open the socket communication. + **************************************************************************** */ + +static BOOL open_sockets(BOOL isdaemon, int port) +{ + /* + * The sockets opened here will be used to receive broadcast + * packets *only*. Interface specific sockets are opened in + * make_subnet() in namedbsubnet.c. Thus we bind to the + * address "0.0.0.0". The parameter 'socket address' is + * now deprecated. + */ + + if ( isdaemon ) + ClientNMB = open_socket_in(SOCK_DGRAM, port,0,0,True); + else + ClientNMB = 0; + + ClientDGRAM = open_socket_in(SOCK_DGRAM,DGRAM_PORT,3,0,True); + + if ( ClientNMB == -1 ) + return( False ); + + /* we are never interested in SIGPIPE */ + BlockSignals(True,SIGPIPE); + + set_socket_options( ClientNMB, "SO_BROADCAST" ); + set_socket_options( ClientDGRAM, "SO_BROADCAST" ); + + DEBUG( 3, ( "open_sockets: Broadcast sockets opened.\n" ) ); + return( True ); +} + +/**************************************************************************** ** + main program + **************************************************************************** */ + int main(int argc, const char *argv[]) +{ + static BOOL opt_interactive = False; + poptContext pc; + struct poptOption long_options[] = { + POPT_AUTOHELP + {"daemon", 'D', POPT_ARG_VAL, &is_daemon, True, "Become a daemon(default)" }, + {"interactive", 'i', POPT_ARG_VAL, &opt_interactive, True, "Run interactive (not a daemon)" }, + {"foreground", 'F', POPT_ARG_VAL, &Fork, False, "Run daemon in foreground (for daemontools & etc)" }, + {"log-stdout", 'S', POPT_ARG_VAL, &log_stdout, True, "Log to stdout" }, + {"hosts", 'H', POPT_ARG_STRING, dyn_LMHOSTSFILE, 'H', "Load a netbios hosts file"}, + {"port", 'p', POPT_ARG_INT, &global_nmb_port, NMB_PORT, "Listen on the specified port" }, + {NULL, 0, POPT_ARG_INCLUDE_TABLE, popt_common_debug }, + {NULL, 0, POPT_ARG_INCLUDE_TABLE, popt_common_configfile }, + {NULL, 0, POPT_ARG_INCLUDE_TABLE, popt_common_socket_options }, + {NULL, 0, POPT_ARG_INCLUDE_TABLE, popt_common_version }, + {NULL, 0, POPT_ARG_INCLUDE_TABLE, popt_common_netbios_name }, + {NULL, 0, POPT_ARG_INCLUDE_TABLE, popt_common_log_base }, + { NULL } + }; + int opt; + pstring logfile; + + global_nmb_port = NMB_PORT; + global_in_nmbd = True; + + StartupTime = time(NULL); + + sys_srandom(time(NULL) ^ sys_getpid()); + + slprintf(logfile, sizeof(logfile)-1, "%s/log.nmbd", dyn_LOGFILEBASE); + lp_set_logfile(logfile); + + fault_setup((void (*)(void *))fault_continue ); + + /* POSIX demands that signals are inherited. If the invoking process has + * these signals masked, we will have problems, as we won't recieve them. */ + BlockSignals(False, SIGHUP); + BlockSignals(False, SIGUSR1); + BlockSignals(False, SIGTERM); + + CatchSignal( SIGHUP, SIGNAL_CAST sig_hup ); + CatchSignal( SIGTERM, SIGNAL_CAST sig_term ); + +#if defined(SIGFPE) + /* we are never interested in SIGFPE */ + BlockSignals(True,SIGFPE); +#endif + + /* We no longer use USR2... */ +#if defined(SIGUSR2) + BlockSignals(True, SIGUSR2); +#endif + pc = poptGetContext("nmbd", argc, argv, long_options, 0); + + while((opt = poptGetNextOpt(pc)) != -1) + { } + + poptFreeContext(pc); + + if ( opt_interactive ) { + Fork = False; + log_stdout = True; + } + + if ( log_stdout && Fork ) { + DEBUG(0,("ERROR: Can't log to stdout (-S) unless daemon is in foreground (-F) or interactive (-i)\n")); + exit(1); + } + + setup_logging( argv[0], log_stdout ); + + reopen_logs(); + + DEBUG( 0, ( "Netbios nameserver version %s started.\n", VERSION ) ); + DEBUGADD( 0, ( "Copyright Andrew Tridgell and the Samba Team 1994-2002\n" ) ); + + if ( !reload_nmbd_services(False) ) + return(-1); + + if(!init_names()) + return -1; + + reload_nmbd_services( True ); + + if (strequal(lp_workgroup(),"*")) + { + DEBUG(0,("ERROR: a workgroup name of * is no longer supported\n")); + exit(1); + } + + set_samba_nb_type(); + + if (!is_daemon && !is_a_socket(0)) + { + DEBUG(0,("standard input is not a socket, assuming -D option\n")); + is_daemon = True; + } + + if (is_daemon && !opt_interactive) + { + DEBUG( 2, ( "Becoming a daemon.\n" ) ); + become_daemon(Fork); + } + +#if HAVE_SETPGID + /* + * If we're interactive we want to set our own process group for + * signal management. + */ + if (opt_interactive) + setpgid( (pid_t)0, (pid_t)0 ); +#endif + +#ifndef SYNC_DNS + /* Setup the async dns. We do it here so it doesn't have all the other + stuff initialised and thus chewing memory and sockets */ + if(lp_we_are_a_wins_server() && lp_dns_proxy()) { + start_async_dns(); + } +#endif + + if (!directory_exist(lp_lockdir(), NULL)) { + mkdir(lp_lockdir(), 0755); + } + + pidfile_create("nmbd"); + message_init(); + message_register(MSG_FORCE_ELECTION, nmbd_message_election); + message_register(MSG_WINS_NEW_ENTRY, nmbd_wins_new_entry); + message_register(MSG_SHUTDOWN, nmbd_terminate); + message_register(MSG_REQ_TALLOC_USAGE, return_all_talloc_info); + + DEBUG( 3, ( "Opening sockets %d\n", global_nmb_port ) ); + + if ( !open_sockets( is_daemon, global_nmb_port ) ) { + kill_async_dns_child(); + return 1; + } + + /* Determine all the IP addresses we have. */ + load_interfaces(); + + /* Create an nmbd subnet record for each of the above. */ + if( False == create_subnets() ) + { + DEBUG(0,("ERROR: Failed when creating subnet lists. Exiting.\n")); + kill_async_dns_child(); + exit(1); + } + + /* Load in any static local names. */ + load_lmhosts_file(dyn_LMHOSTSFILE); + DEBUG(3,("Loaded hosts file %s\n", dyn_LMHOSTSFILE)); + + /* If we are acting as a WINS server, initialise data structures. */ + if( !initialise_wins() ) + { + DEBUG( 0, ( "nmbd: Failed when initialising WINS server.\n" ) ); + kill_async_dns_child(); + exit(1); + } + + /* + * Register nmbd primary workgroup and nmbd names on all + * the broadcast subnets, and on the WINS server (if specified). + * Also initiate the startup of our primary workgroup (start + * elections if we are setup as being able to be a local + * master browser. + */ + + if( False == register_my_workgroup_and_names() ) + { + DEBUG(0,("ERROR: Failed when creating my my workgroup. Exiting.\n")); + kill_async_dns_child(); + exit(1); + } + + /* We can only take signals in the select. */ + BlockSignals( True, SIGTERM ); + + process(); + + if (dbf) + x_fclose(dbf); + kill_async_dns_child(); + return(0); +} diff --git a/source4/nmbd/nmbd_become_dmb.c b/source4/nmbd/nmbd_become_dmb.c new file mode 100644 index 0000000000..d8122777fe --- /dev/null +++ b/source4/nmbd/nmbd_become_dmb.c @@ -0,0 +1,396 @@ +/* + Unix SMB/CIFS implementation. + NBT netbios routines and daemon - version 2 + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Luke Kenneth Casson Leighton 1994-1998 + Copyright (C) Jeremy Allison 1994-1998 + + 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" + +extern struct in_addr allones_ip; + +extern uint16 samba_nb_type; /* Samba's NetBIOS type. */ + +static void become_domain_master_browser_bcast(const char *); + +/**************************************************************************** + Fail to become a Domain Master Browser on a subnet. + ****************************************************************************/ + +static void become_domain_master_fail(struct subnet_record *subrec, + struct response_record *rrec, + struct nmb_name *fail_name) +{ + struct work_record *work = find_workgroup_on_subnet(subrec, fail_name->name); + struct server_record *servrec; + + if(!work) + { + DEBUG(0,("become_domain_master_fail: Error - cannot find \ +workgroup %s on subnet %s\n", fail_name->name, subrec->subnet_name)); + return; + } + + /* Set the state back to DOMAIN_NONE. */ + work->dom_state = DOMAIN_NONE; + + if((servrec = find_server_in_workgroup( work, lp_netbios_name())) == NULL) + { + DEBUG(0,("become_domain_master_fail: Error - cannot find server %s \ +in workgroup %s on subnet %s\n", + lp_netbios_name(), work->work_group, subrec->subnet_name)); + return; + } + + /* Update our server status. */ + servrec->serv.type &= ~SV_TYPE_DOMAIN_MASTER; + + /* Tell the namelist writer to write out a change. */ + subrec->work_changed = True; + + DEBUG(0,("become_domain_master_fail: Failed to become a domain master browser for \ +workgroup %s on subnet %s. Couldn't register name %s.\n", + work->work_group, subrec->subnet_name, nmb_namestr(fail_name))); +} + +/**************************************************************************** + Become a Domain Master Browser on a subnet. + ****************************************************************************/ + +static void become_domain_master_stage2(struct subnet_record *subrec, + struct userdata_struct *userdata, + struct nmb_name *registered_name, + uint16 nb_flags, + int ttl, struct in_addr registered_ip) +{ + struct work_record *work = find_workgroup_on_subnet( subrec, registered_name->name); + struct server_record *servrec; + + if(!work) + { + DEBUG(0,("become_domain_master_stage2: Error - cannot find \ +workgroup %s on subnet %s\n", registered_name->name, subrec->subnet_name)); + return; + } + + if((servrec = find_server_in_workgroup( work, lp_netbios_name())) == NULL) + { + DEBUG(0,("become_domain_master_stage2: Error - cannot find server %s \ +in workgroup %s on subnet %s\n", + lp_netbios_name(), registered_name->name, subrec->subnet_name)); + work->dom_state = DOMAIN_NONE; + return; + } + + /* Set the state in the workgroup structure. */ + work->dom_state = DOMAIN_MST; /* Become domain master. */ + + /* Update our server status. */ + servrec->serv.type |= (SV_TYPE_NT|SV_TYPE_DOMAIN_MASTER); + + /* Tell the namelist writer to write out a change. */ + subrec->work_changed = True; + + if( DEBUGLVL( 0 ) ) + { + dbgtext( "*****\n\nSamba server %s ", lp_netbios_name() ); + dbgtext( "is now a domain master browser for " ); + dbgtext( "workgroup %s ", work->work_group ); + dbgtext( "on subnet %s\n\n*****\n", subrec->subnet_name ); + } + + if( subrec == unicast_subnet ) + { + struct nmb_name nmbname; + struct in_addr my_first_ip; + + /* Put our name and first IP address into the + workgroup struct as domain master browser. This + will stop us syncing with ourself if we are also + a local master browser. */ + + make_nmb_name(&nmbname, lp_netbios_name(), 0x20); + + work->dmb_name = nmbname; + /* Pick the first interface ip address as the domain master browser ip. */ + my_first_ip = *iface_n_ip(0); + + putip((char *)&work->dmb_addr, &my_first_ip); + + /* We successfully registered by unicast with the + WINS server. We now expect to become the domain + master on the local subnets. If this fails, it's + probably a 1.9.16p2 to 1.9.16p11 server's fault. + + This is a configuration issue that should be addressed + by the network administrator - you shouldn't have + several machines configured as a domain master browser + for the same WINS scope (except if they are 1.9.17 or + greater, and you know what you're doing. + + see docs/DOMAIN.txt. + + */ + become_domain_master_browser_bcast(work->work_group); + } + else + { + /* + * Now we are a domain master on a broadcast subnet, we need to add + * the WORKGROUP<1b> name to the unicast subnet so that we can answer + * unicast requests sent to this name. This bug wasn't found for a while + * as it is strange to have a DMB without using WINS. JRA. + */ + insert_permanent_name_into_unicast(subrec, registered_name, nb_flags); + } +} + +/**************************************************************************** + Start the name registration process when becoming a Domain Master Browser + on a subnet. + ****************************************************************************/ + +static void become_domain_master_stage1(struct subnet_record *subrec, char *wg_name) +{ + struct work_record *work; + + DEBUG(2,("become_domain_master_stage1: Becoming domain master browser for \ +workgroup %s on subnet %s\n", wg_name, subrec->subnet_name)); + + /* First, find the workgroup on the subnet. */ + if((work = find_workgroup_on_subnet( subrec, wg_name )) == NULL) + { + DEBUG(0,("become_domain_master_stage1: Error - unable to find workgroup %s on subnet %s.\n", + wg_name, subrec->subnet_name)); + return; + } + + DEBUG(3,("become_domain_master_stage1: go to first stage: register <1b> name\n")); + work->dom_state = DOMAIN_WAIT; + + /* WORKGROUP<1b> is the domain master browser name. */ + register_name(subrec, work->work_group,0x1b,samba_nb_type, + become_domain_master_stage2, + become_domain_master_fail, NULL); +} + +/**************************************************************************** + Function called when a query for a WORKGROUP<1b> name succeeds. + This is normally a fail condition as it means there is already + a domain master browser for a workgroup and we were trying to + become one. +****************************************************************************/ + +static void become_domain_master_query_success(struct subnet_record *subrec, + struct userdata_struct *userdata, + struct nmb_name *nmbname, struct in_addr ip, + struct res_rec *rrec) +{ + /* If the given ip is not ours, then we can't become a domain + controler as the name is already registered. + */ + + /* BUG note. Samba 1.9.16p11 servers seem to return the broadcast + address or zero ip for this query. Pretend this is ok. */ + + if(ismyip(ip) || ip_equal(allones_ip, ip) || is_zero_ip(ip)) + { + if( DEBUGLVL( 3 ) ) + { + dbgtext( "become_domain_master_query_success():\n" ); + dbgtext( "Our address (%s) ", inet_ntoa(ip) ); + dbgtext( "returned in query for name %s ", nmb_namestr(nmbname) ); + dbgtext( "(domain master browser name) " ); + dbgtext( "on subnet %s.\n", subrec->subnet_name ); + dbgtext( "Continuing with domain master code.\n" ); + } + + become_domain_master_stage1(subrec, nmbname->name); + } + else + { + if( DEBUGLVL( 0 ) ) + { + dbgtext( "become_domain_master_query_success:\n" ); + dbgtext( "There is already a domain master browser at " ); + dbgtext( "IP %s for workgroup %s ", inet_ntoa(ip), nmbname->name ); + dbgtext( "registered on subnet %s.\n", subrec->subnet_name ); + } + } +} + +/**************************************************************************** + Function called when a query for a WORKGROUP<1b> name fails. + This is normally a success condition as it then allows us to register + our own Domain Master Browser name. + ****************************************************************************/ + +static void become_domain_master_query_fail(struct subnet_record *subrec, + struct response_record *rrec, + struct nmb_name *question_name, int fail_code) +{ + /* If the query was unicast, and the error is not NAM_ERR (name didn't exist), + then this is a failure. Otherwise, not finding the name is what we want. */ + if((subrec == unicast_subnet) && (fail_code != NAM_ERR)) + { + DEBUG(0,("become_domain_master_query_fail: Error %d returned when \ +querying WINS server for name %s.\n", + fail_code, nmb_namestr(question_name))); + return; + } + + /* Otherwise - not having the name allows us to register it. */ + become_domain_master_stage1(subrec, question_name->name); +} + +/**************************************************************************** + Attempt to become a domain master browser on all broadcast subnets. + ****************************************************************************/ + +static void become_domain_master_browser_bcast(const char *workgroup_name) +{ + struct subnet_record *subrec; + + for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) + { + struct work_record *work = find_workgroup_on_subnet(subrec, workgroup_name); + + if (work && (work->dom_state == DOMAIN_NONE)) + { + struct nmb_name nmbname; + make_nmb_name(&nmbname,workgroup_name,0x1b); + + /* + * Check for our name on the given broadcast subnet first, only initiate + * further processing if we cannot find it. + */ + + if (find_name_on_subnet(subrec, &nmbname, FIND_SELF_NAME) == NULL) + { + if( DEBUGLVL( 0 ) ) + { + dbgtext( "become_domain_master_browser_bcast:\n" ); + dbgtext( "Attempting to become domain master browser on " ); + dbgtext( "workgroup %s on subnet %s\n", + workgroup_name, subrec->subnet_name ); + } + + /* Send out a query to establish whether there's a + domain controller on the local subnet. If not, + we can become a domain controller. + */ + + DEBUG(0,("become_domain_master_browser_bcast: querying subnet %s \ +for domain master browser on workgroup %s\n", subrec->subnet_name, workgroup_name)); + + query_name(subrec, nmbname.name, nmbname.name_type, + become_domain_master_query_success, + become_domain_master_query_fail, + NULL); + } + } + } +} + +/**************************************************************************** + Attempt to become a domain master browser by registering with WINS. + ****************************************************************************/ + +static void become_domain_master_browser_wins(const char *workgroup_name) +{ + struct work_record *work; + + work = find_workgroup_on_subnet(unicast_subnet, workgroup_name); + + if (work && (work->dom_state == DOMAIN_NONE)) + { + struct nmb_name nmbname; + + make_nmb_name(&nmbname,workgroup_name,0x1b); + + /* + * Check for our name on the unicast subnet first, only initiate + * further processing if we cannot find it. + */ + + if (find_name_on_subnet(unicast_subnet, &nmbname, FIND_SELF_NAME) == NULL) + { + if( DEBUGLVL( 0 ) ) + { + dbgtext( "become_domain_master_browser_wins:\n" ); + dbgtext( "Attempting to become domain master browser " ); + dbgtext( "on workgroup %s, subnet %s.\n", + workgroup_name, unicast_subnet->subnet_name ); + } + + /* Send out a query to establish whether there's a + domain master broswer registered with WINS. If not, + we can become a domain master browser. + */ + + DEBUG(0,("become_domain_master_browser_wins: querying WINS server from IP %s \ +for domain master browser name %s on workgroup %s\n", + inet_ntoa(unicast_subnet->myip), nmb_namestr(&nmbname), workgroup_name)); + + query_name(unicast_subnet, nmbname.name, nmbname.name_type, + become_domain_master_query_success, + become_domain_master_query_fail, + NULL); + } + } +} + +/**************************************************************************** + Add the domain logon server and domain master browser names + if we are set up to do so. + **************************************************************************/ + +void add_domain_names(time_t t) +{ + static time_t lastrun = 0; + + if ((lastrun != 0) && (t < lastrun + (CHECK_TIME_ADD_DOM_NAMES * 60))) + return; + + lastrun = t; + + /* Do the "internet group" - <1c> names. */ + if (lp_domain_logons()) + add_logon_names(); + + /* Do the domain master names. */ + if(lp_server_role() == ROLE_DOMAIN_PDC) + { + if(we_are_a_wins_client()) + { + /* We register the WORKGROUP<1b> name with the WINS + server first, and call add_domain_master_bcast() + only if this is successful. + + This results in domain logon services being gracefully provided, + as opposed to the aggressive nature of 1.9.16p2 to 1.9.16p11. + 1.9.16p2 to 1.9.16p11 - due to a bug in namelogon.c, + cannot provide domain master / domain logon services. + */ + become_domain_master_browser_wins(lp_workgroup()); + } + else + become_domain_master_browser_bcast(lp_workgroup()); + } +} diff --git a/source4/nmbd/nmbd_become_lmb.c b/source4/nmbd/nmbd_become_lmb.c new file mode 100644 index 0000000000..8b87ca7444 --- /dev/null +++ b/source4/nmbd/nmbd_become_lmb.c @@ -0,0 +1,605 @@ +/* + Unix SMB/CIFS implementation. + NBT netbios routines and daemon - version 2 + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Luke Kenneth Casson Leighton 1994-1998 + Copyright (C) Jeremy Allison 1994-1998 + + 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" + +extern uint16 samba_nb_type; /* Samba's NetBIOS name type. */ + +/******************************************************************* + Utility function to add a name to the unicast subnet, or add in + our IP address if it already exists. +******************************************************************/ + +void insert_permanent_name_into_unicast( struct subnet_record *subrec, + struct nmb_name *nmbname, uint16 nb_type ) +{ + struct name_record *namerec; + + if((namerec = find_name_on_subnet(unicast_subnet, nmbname, FIND_SELF_NAME)) == NULL) + { + /* The name needs to be created on the unicast subnet. */ + (void)add_name_to_subnet( unicast_subnet, nmbname->name, + nmbname->name_type, nb_type, + PERMANENT_TTL, PERMANENT_NAME, 1, &subrec->myip); + } + else + { + /* The name already exists on the unicast subnet. Add our local + IP for the given broadcast subnet to the name. */ + add_ip_to_name_record( namerec, subrec->myip); + } +} + +/******************************************************************* + Utility function to remove a name from the unicast subnet. +******************************************************************/ + +static void remove_permanent_name_from_unicast( struct subnet_record *subrec, + struct nmb_name *nmbname ) +{ + struct name_record *namerec; + + if((namerec = find_name_on_subnet(unicast_subnet, nmbname, FIND_SELF_NAME)) != NULL) + { + /* Remove this broadcast subnet IP address from the name. */ + remove_ip_from_name_record( namerec, subrec->myip); + if(namerec->data.num_ips == 0) + remove_name_from_namelist( unicast_subnet, namerec); + } +} + +/******************************************************************* + Utility function always called to set our workgroup and server + state back to potential browser, or none. +******************************************************************/ + +static void reset_workgroup_state( struct subnet_record *subrec, char *workgroup_name, + BOOL force_new_election ) +{ + struct work_record *work; + struct server_record *servrec; + struct nmb_name nmbname; + + if((work = find_workgroup_on_subnet( subrec, workgroup_name)) == NULL) + { + DEBUG(0,("reset_workgroup_state: Error - cannot find workgroup %s on \ +subnet %s.\n", workgroup_name, subrec->subnet_name )); + return; + } + + if((servrec = find_server_in_workgroup( work, lp_netbios_name())) == NULL) + { + DEBUG(0,("reset_workgroup_state: Error - cannot find server %s \ +in workgroup %s on subnet %s\n", + lp_netbios_name(), work->work_group, subrec->subnet_name)); + work->mst_state = lp_local_master() ? MST_POTENTIAL : MST_NONE; + return; + } + + /* Update our server status - remove any master flag and replace + it with the potential browser flag. */ + servrec->serv.type &= ~SV_TYPE_MASTER_BROWSER; + servrec->serv.type |= (lp_local_master() ? SV_TYPE_POTENTIAL_BROWSER : 0); + + /* Tell the namelist writer to write out a change. */ + subrec->work_changed = True; + + /* Reset our election flags. */ + work->ElectionCriterion &= ~0x4; + + work->mst_state = lp_local_master() ? MST_POTENTIAL : MST_NONE; + + /* Forget who the local master browser was for + this workgroup. */ + + set_workgroup_local_master_browser_name( work, ""); + + /* + * Ensure the IP address of this subnet is not registered as one + * of the IP addresses of the WORKGROUP<1d> name on the unicast + * subnet. This undoes what we did below when we became a local + * master browser. + */ + + make_nmb_name(&nmbname, work->work_group, 0x1d); + + remove_permanent_name_from_unicast( subrec, &nmbname); + + if(force_new_election) + work->needelection = True; +} + +/******************************************************************* + Unbecome the local master browser name release success function. +******************************************************************/ + +static void unbecome_local_master_success(struct subnet_record *subrec, + struct userdata_struct *userdata, + struct nmb_name *released_name, + struct in_addr released_ip) +{ + BOOL force_new_election = False; + + memcpy((char *)&force_new_election, userdata->data, sizeof(BOOL)); + + DEBUG(3,("unbecome_local_master_success: released name %s.\n", + nmb_namestr(released_name))); + + /* Now reset the workgroup and server state. */ + reset_workgroup_state( subrec, released_name->name, force_new_election ); + + if( DEBUGLVL( 0 ) ) + { + dbgtext( "*****\n\n" ); + dbgtext( "Samba name server %s ", lp_netbios_name() ); + dbgtext( "has stopped being a local master browser " ); + dbgtext( "for workgroup %s ", released_name->name ); + dbgtext( "on subnet %s\n\n*****\n", subrec->subnet_name ); + } + +} + +/******************************************************************* + Unbecome the local master browser name release fail function. +******************************************************************/ + +static void unbecome_local_master_fail(struct subnet_record *subrec, struct response_record *rrec, + struct nmb_name *fail_name) +{ + struct name_record *namerec; + struct userdata_struct *userdata = rrec->userdata; + BOOL force_new_election = False; + + memcpy((char *)&force_new_election, userdata->data, sizeof(BOOL)); + + DEBUG(0,("unbecome_local_master_fail: failed to release name %s. \ +Removing from namelist anyway.\n", nmb_namestr(fail_name))); + + /* Do it anyway. */ + namerec = find_name_on_subnet(subrec, fail_name, FIND_SELF_NAME); + if(namerec) + remove_name_from_namelist(subrec, namerec); + + /* Now reset the workgroup and server state. */ + reset_workgroup_state( subrec, fail_name->name, force_new_election ); + + if( DEBUGLVL( 0 ) ) + { + dbgtext( "*****\n\n" ); + dbgtext( "Samba name server %s ", lp_netbios_name() ); + dbgtext( "has stopped being a local master browser " ); + dbgtext( "for workgroup %s ", fail_name->name ); + dbgtext( "on subnet %s\n\n*****\n", subrec->subnet_name ); + } +} + +/******************************************************************* + Utility function to remove the WORKGROUP<1d> name. +******************************************************************/ + +static void release_1d_name( struct subnet_record *subrec, char *workgroup_name, + BOOL force_new_election) +{ + struct nmb_name nmbname; + struct name_record *namerec; + + make_nmb_name(&nmbname, workgroup_name, 0x1d); + if((namerec = find_name_on_subnet( subrec, &nmbname, FIND_SELF_NAME))!=NULL) + { + struct userdata_struct *userdata; + int size = sizeof(struct userdata_struct) + sizeof(BOOL); + + if((userdata = (struct userdata_struct *)malloc(size)) == NULL) + { + DEBUG(0,("release_1d_name: malloc fail.\n")); + return; + } + + userdata->copy_fn = NULL; + userdata->free_fn = NULL; + userdata->userdata_len = sizeof(BOOL); + memcpy((char *)userdata->data, &force_new_election, sizeof(BOOL)); + + release_name(subrec, namerec, + unbecome_local_master_success, + unbecome_local_master_fail, + userdata); + + zero_free(userdata, size); + } +} + +/******************************************************************* + Unbecome the local master browser MSBROWSE name release success function. +******************************************************************/ + +static void release_msbrowse_name_success(struct subnet_record *subrec, + struct userdata_struct *userdata, + struct nmb_name *released_name, + struct in_addr released_ip) +{ + DEBUG(4,("release_msbrowse_name_success: Released name %s on subnet %s\n.", + nmb_namestr(released_name), subrec->subnet_name )); + + /* Remove the permanent MSBROWSE name added into the unicast subnet. */ + remove_permanent_name_from_unicast( subrec, released_name); +} + +/******************************************************************* + Unbecome the local master browser MSBROWSE name release fail function. +******************************************************************/ + +static void release_msbrowse_name_fail( struct subnet_record *subrec, + struct response_record *rrec, + struct nmb_name *fail_name) +{ + struct name_record *namerec; + + DEBUG(4,("release_msbrowse_name_fail: Failed to release name %s on subnet %s\n.", + nmb_namestr(fail_name), subrec->subnet_name )); + + /* Release the name anyway. */ + namerec = find_name_on_subnet(subrec, fail_name, FIND_SELF_NAME); + if(namerec) + remove_name_from_namelist(subrec, namerec); + + /* Remove the permanent MSBROWSE name added into the unicast subnet. */ + remove_permanent_name_from_unicast( subrec, fail_name); +} + +/******************************************************************* + Unbecome the local master browser. If force_new_election is true, restart + the election process after we've unbecome the local master. +******************************************************************/ + +void unbecome_local_master_browser(struct subnet_record *subrec, struct work_record *work, + BOOL force_new_election) +{ + struct name_record *namerec; + struct nmb_name nmbname; + + /* Sanity check. */ + + DEBUG(2,("unbecome_local_master_browser: unbecoming local master for workgroup %s \ +on subnet %s\n",work->work_group, subrec->subnet_name)); + + if(find_server_in_workgroup( work, lp_netbios_name()) == NULL) + { + DEBUG(0,("unbecome_local_master_browser: Error - cannot find server %s \ +in workgroup %s on subnet %s\n", + lp_netbios_name(), work->work_group, subrec->subnet_name)); + work->mst_state = lp_local_master() ? MST_POTENTIAL : MST_NONE; + return; + } + + /* Set the state to unbecoming. */ + work->mst_state = MST_UNBECOMING_MASTER; + + /* + * Release the WORKGROUP<1d> name asap to allow another machine to + * claim it. + */ + + release_1d_name( subrec, work->work_group, force_new_election); + + /* Deregister any browser names we may have. */ + make_nmb_name(&nmbname, MSBROWSE, 0x1); + if((namerec = find_name_on_subnet( subrec, &nmbname, FIND_SELF_NAME))!=NULL) + { + release_name(subrec, namerec, + release_msbrowse_name_success, + release_msbrowse_name_fail, + NULL); + } + + /* + * Ensure we have sent and processed these release packets + * before returning - we don't want to process any election + * packets before dealing with the 1d release. + */ + + retransmit_or_expire_response_records(time(NULL)); +} + +/**************************************************************************** + Success in registering the WORKGROUP<1d> name. + We are now *really* a local master browser. + ****************************************************************************/ + +static void become_local_master_stage2(struct subnet_record *subrec, + struct userdata_struct *userdata, + struct nmb_name *registered_name, + uint16 nb_flags, + int ttl, struct in_addr registered_ip) +{ + int i = 0; + struct server_record *sl; + struct work_record *work = find_workgroup_on_subnet( subrec, registered_name->name); + struct server_record *servrec; + + if(!work) + { + DEBUG(0,("become_local_master_stage2: Error - cannot find \ +workgroup %s on subnet %s\n", registered_name->name, subrec->subnet_name)); + return; + } + + if((servrec = find_server_in_workgroup( work, lp_netbios_name())) == NULL) + { + DEBUG(0,("become_local_master_stage2: Error - cannot find server %s \ +in workgroup %s on subnet %s\n", + lp_netbios_name(), registered_name->name, subrec->subnet_name)); + work->mst_state = lp_local_master() ? MST_POTENTIAL : MST_NONE; + return; + } + + DEBUG(3,("become_local_master_stage2: registered as master browser for workgroup %s \ +on subnet %s\n", work->work_group, subrec->subnet_name)); + + work->mst_state = MST_BROWSER; /* registering WORKGROUP(1d) succeeded */ + + /* update our server status */ + servrec->serv.type |= SV_TYPE_MASTER_BROWSER; + servrec->serv.type &= ~SV_TYPE_POTENTIAL_BROWSER; + + /* Tell the namelist writer to write out a change. */ + subrec->work_changed = True; + + /* Add this name to the workgroup as local master browser. */ + set_workgroup_local_master_browser_name( work, lp_netbios_name()); + + /* Count the number of servers we have on our list. If it's + less than 10 (just a heuristic) request the servers + to announce themselves. + */ + for( sl = work->serverlist; sl != NULL; sl = sl->next) + i++; + + if (i < 10) + { + /* Ask all servers on our local net to announce to us. */ + broadcast_announce_request(subrec, work); + } + + /* + * Now we are a local master on a broadcast subnet, we need to add + * the WORKGROUP<1d> name to the unicast subnet so that we can answer + * unicast requests sent to this name. We can create this name directly on + * the unicast subnet as a WINS server always returns true when registering + * this name, and discards the registration. We use the number of IP + * addresses registered to this name as a reference count, as we + * remove this broadcast subnet IP address from it when we stop becoming a local + * master browser for this broadcast subnet. + */ + + insert_permanent_name_into_unicast( subrec, registered_name, nb_flags); + + /* Reset the announce master browser timer so that we try and tell a domain + master browser as soon as possible that we are a local master browser. */ + reset_announce_timer(); + + if( DEBUGLVL( 0 ) ) + { + dbgtext( "*****\n\n" ); + dbgtext( "Samba name server %s ", lp_netbios_name() ); + dbgtext( "is now a local master browser " ); + dbgtext( "for workgroup %s ", work->work_group ); + dbgtext( "on subnet %s\n\n*****\n", subrec->subnet_name ); + } + +} + +/**************************************************************************** + Failed to register the WORKGROUP<1d> name. + ****************************************************************************/ +static void become_local_master_fail2(struct subnet_record *subrec, + struct response_record *rrec, + struct nmb_name *fail_name) +{ + struct work_record *work = find_workgroup_on_subnet( subrec, fail_name->name); + + DEBUG(0,("become_local_master_fail2: failed to register name %s on subnet %s. \ +Failed to become a local master browser.\n", nmb_namestr(fail_name), subrec->subnet_name)); + + if(!work) + { + DEBUG(0,("become_local_master_fail2: Error - cannot find \ +workgroup %s on subnet %s\n", fail_name->name, subrec->subnet_name)); + return; + } + + /* Roll back all the way by calling unbecome_local_master_browser(). */ + unbecome_local_master_browser(subrec, work, False); +} + +/**************************************************************************** + Success in registering the MSBROWSE name. + ****************************************************************************/ + +static void become_local_master_stage1(struct subnet_record *subrec, + struct userdata_struct *userdata, + struct nmb_name *registered_name, + uint16 nb_flags, + int ttl, struct in_addr registered_ip) +{ + char *work_name = userdata->data; + struct work_record *work = find_workgroup_on_subnet( subrec, work_name); + + if(!work) + { + DEBUG(0,("become_local_master_stage1: Error - cannot find \ +workgroup %s on subnet %s\n", work_name, subrec->subnet_name)); + return; + } + + DEBUG(3,("become_local_master_stage1: go to stage 2: register the %s<1d> name.\n", + work->work_group)); + + work->mst_state = MST_MSB; /* Registering MSBROWSE was successful. */ + + /* + * We registered the MSBROWSE name on a broadcast subnet, now need to add + * the MSBROWSE name to the unicast subnet so that we can answer + * unicast requests sent to this name. We create this name directly on + * the unicast subnet. + */ + + insert_permanent_name_into_unicast( subrec, registered_name, nb_flags); + + /* Attempt to register the WORKGROUP<1d> name. */ + register_name(subrec, work->work_group,0x1d,samba_nb_type, + become_local_master_stage2, + become_local_master_fail2, + NULL); +} + +/**************************************************************************** + Failed to register the MSBROWSE name. + ****************************************************************************/ + +static void become_local_master_fail1(struct subnet_record *subrec, + struct response_record *rrec, + struct nmb_name *fail_name) +{ + char *work_name = rrec->userdata->data; + struct work_record *work = find_workgroup_on_subnet(subrec, work_name); + + if(!work) + { + DEBUG(0,("become_local_master_fail1: Error - cannot find \ +workgroup %s on subnet %s\n", work_name, subrec->subnet_name)); + return; + } + + if(find_server_in_workgroup(work, lp_netbios_name()) == NULL) + { + DEBUG(0,("become_local_master_fail1: Error - cannot find server %s \ +in workgroup %s on subnet %s\n", + lp_netbios_name(), work->work_group, subrec->subnet_name)); + return; + } + + reset_workgroup_state( subrec, work->work_group, False ); + + DEBUG(0,("become_local_master_fail1: Failed to become a local master browser for \ +workgroup %s on subnet %s. Couldn't register name %s.\n", + work->work_group, subrec->subnet_name, nmb_namestr(fail_name))); +} + +/****************************************************************** + Become the local master browser on a subnet. + This gets called if we win an election on this subnet. + + Stage 1: mst_state was MST_POTENTIAL - go to MST_BACK register ^1^2__MSBROWSE__^2^1. + Stage 2: mst_state was MST_BACKUP - go to MST_MSB and register WORKGROUP<1d>. + Stage 3: mst_state was MST_MSB - go to MST_BROWSER. +******************************************************************/ + +void become_local_master_browser(struct subnet_record *subrec, struct work_record *work) +{ + struct userdata_struct *userdata; + int size = sizeof(struct userdata_struct) + sizeof(fstring) + 1; + + /* Sanity check. */ + if (!lp_local_master()) + { + DEBUG(0,("become_local_master_browser: Samba not configured as a local master browser.\n")); + return; + } + + if(!AM_POTENTIAL_MASTER_BROWSER(work)) + { + DEBUG(2,("become_local_master_browser: Awaiting potential browser state. Current state is %d\n", + work->mst_state )); + return; + } + + if(find_server_in_workgroup( work, lp_netbios_name()) == NULL) + { + DEBUG(0,("become_local_master_browser: Error - cannot find server %s \ +in workgroup %s on subnet %s\n", + lp_netbios_name(), work->work_group, subrec->subnet_name)); + return; + } + + DEBUG(2,("become_local_master_browser: Starting to become a master browser for workgroup \ +%s on subnet %s\n", work->work_group, subrec->subnet_name)); + + DEBUG(3,("become_local_master_browser: first stage - attempt to register ^1^2__MSBROWSE__^2^1\n")); + work->mst_state = MST_BACKUP; /* an election win was successful */ + + work->ElectionCriterion |= 0x5; + + /* Tell the namelist writer to write out a change. */ + subrec->work_changed = True; + + /* Setup the userdata_struct. */ + if((userdata = (struct userdata_struct *)malloc(size)) == NULL) + { + DEBUG(0,("become_local_master_browser: malloc fail.\n")); + return; + } + + userdata->copy_fn = NULL; + userdata->free_fn = NULL; + userdata->userdata_len = strlen(work->work_group)+1; + fstrcpy(userdata->data, work->work_group); + + /* Register the special browser group name. */ + register_name(subrec, MSBROWSE, 0x01, samba_nb_type|NB_GROUP, + become_local_master_stage1, + become_local_master_fail1, + userdata); + + zero_free(userdata, size); +} + +/*************************************************************** + Utility function to set the local master browser name. Does + some sanity checking as old versions of Samba seem to sometimes + say that the master browser name for a workgroup is the same + as the workgroup name. +****************************************************************/ + +void set_workgroup_local_master_browser_name( struct work_record *work, const char *newname) +{ + DEBUG(5,("set_workgroup_local_master_browser_name: setting local master name to '%s' \ +for workgroup %s.\n", newname, work->work_group )); + +#if 0 + /* + * Apparently some sites use the workgroup name as the local + * master browser name. Arrrrggghhhhh ! (JRA). + */ + if(strequal( work->work_group, newname)) + { + DEBUG(5, ("set_workgroup_local_master_browser_name: Refusing to set \ +local_master_browser_name for workgroup %s to workgroup name.\n", + work->work_group )); + return; + } +#endif + + StrnCpy(work->local_master_browser_name, newname, + sizeof(work->local_master_browser_name)-1); +} diff --git a/source4/nmbd/nmbd_browserdb.c b/source4/nmbd/nmbd_browserdb.c new file mode 100644 index 0000000000..a4ef98e265 --- /dev/null +++ b/source4/nmbd/nmbd_browserdb.c @@ -0,0 +1,182 @@ +/* + Unix SMB/CIFS implementation. + NBT netbios routines and daemon - version 2 + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Luke Kenneth Casson Leighton 1994-1998 + Copyright (C) Jeremy Allison 1994-1998 + Copyright (C) Christopher R. Hertel 1998 + + 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. + +*/ +/* -------------------------------------------------------------------------- ** + * Modified July 1998 by CRH. + * I converted this module to use the canned doubly-linked lists. I also + * added comments above the functions where possible. + */ + +#include "includes.h" + +/* -------------------------------------------------------------------------- ** + * Variables... + * + * lmb_browserlist - This is our local master browser list. + */ + +ubi_dlNewList( lmb_browserlist ); + + +/* -------------------------------------------------------------------------- ** + * Functions... + */ + +/* ************************************************************************** ** + * Remove and free a browser list entry. + * + * Input: browc - A pointer to the entry to be removed from the list and + * freed. + * Output: none. + * + * ************************************************************************** ** + */ +static void remove_lmb_browser_entry( struct browse_cache_record *browc ) + { + safe_free( ubi_dlRemThis( lmb_browserlist, browc ) ); + } /* remove_lmb_browser_entry */ + +/* ************************************************************************** ** + * Update a browser death time. + * + * Input: browc - Pointer to the entry to be updated. + * Output: none. + * + * ************************************************************************** ** + */ +void update_browser_death_time( struct browse_cache_record *browc ) + { + /* Allow the new lmb to miss an announce period before we remove it. */ + browc->death_time = time(NULL) + ( (CHECK_TIME_MST_ANNOUNCE + 2) * 60 ); + } /* update_browser_death_time */ + +/* ************************************************************************** ** + * Create a browser entry and add it to the local master browser list. + * + * Input: work_name + * browser_name + * ip + * + * Output: Pointer to the new entry, or NULL if malloc() failed. + * + * ************************************************************************** ** + */ +struct browse_cache_record *create_browser_in_lmb_cache( char *work_name, + char *browser_name, + struct in_addr ip ) + { + struct browse_cache_record *browc; + time_t now = time( NULL ); + + browc = (struct browse_cache_record *)malloc( sizeof( *browc ) ); + + if( NULL == browc ) + { + DEBUG( 0, ("create_browser_in_lmb_cache: malloc fail !\n") ); + return( NULL ); + } + + memset( (char *)browc, '\0', sizeof( *browc ) ); + + /* For a new lmb entry we want to sync with it after one minute. This + will allow it time to send out a local announce and build its + browse list. + */ + browc->sync_time = now + 60; + + /* Allow the new lmb to miss an announce period before we remove it. */ + browc->death_time = now + ( (CHECK_TIME_MST_ANNOUNCE + 2) * 60 ); + + StrnCpy( browc->lmb_name, browser_name, sizeof(browc->lmb_name)-1 ); + StrnCpy( browc->work_group, work_name, sizeof(browc->work_group)-1 ); + strupper( browc->lmb_name ); + strupper( browc->work_group ); + + browc->ip = ip; + + (void)ubi_dlAddTail( lmb_browserlist, browc ); + + if( DEBUGLVL( 3 ) ) + { + Debug1( "nmbd_browserdb:create_browser_in_lmb_cache()\n" ); + Debug1( " Added lmb cache entry for workgroup %s ", browc->work_group ); + Debug1( "name %s IP %s ", browc->lmb_name, inet_ntoa(ip) ); + Debug1( "ttl %d\n", (int)browc->death_time ); + } + + return( browc ); + } /* create_browser_in_lmb_cache */ + +/* ************************************************************************** ** + * Find a browser entry in the local master browser list. + * + * Input: browser_name - The name for which to search. + * + * Output: A pointer to the matching entry, or NULL if no match was found. + * + * ************************************************************************** ** + */ +struct browse_cache_record *find_browser_in_lmb_cache( char *browser_name ) + { + struct browse_cache_record *browc; + + for( browc = (struct browse_cache_record *)ubi_dlFirst( lmb_browserlist ); + browc; + browc = (struct browse_cache_record *)ubi_dlNext( browc ) ) + if( strequal( browser_name, browc->lmb_name ) ) + break; + + return( browc ); + } /* find_browser_in_lmb_cache */ + +/* ************************************************************************** ** + * Expire timed out browsers in the browserlist. + * + * Input: t - Expiration time. Entries with death times less than this + * value will be removed from the list. + * Output: none. + * + * ************************************************************************** ** + */ +void expire_lmb_browsers( time_t t ) + { + struct browse_cache_record *browc; + struct browse_cache_record *nextbrowc; + + for( browc = (struct browse_cache_record *)ubi_dlFirst( lmb_browserlist ); + browc; + browc = nextbrowc ) + { + nextbrowc = (struct browse_cache_record *)ubi_dlNext( browc ); + + if( browc->death_time < t ) + { + if( DEBUGLVL( 3 ) ) + { + Debug1( "nmbd_browserdb:expire_lmb_browsers()\n" ); + Debug1( " Removing timed out lmb entry %s\n", browc->lmb_name ); + } + remove_lmb_browser_entry( browc ); + } + } + } /* expire_lmb_browsers */ diff --git a/source4/nmbd/nmbd_browsesync.c b/source4/nmbd/nmbd_browsesync.c new file mode 100644 index 0000000000..ff022a7bb1 --- /dev/null +++ b/source4/nmbd/nmbd_browsesync.c @@ -0,0 +1,699 @@ +/* + Unix SMB/CIFS implementation. + NBT netbios routines and daemon - version 2 + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Luke Kenneth Casson Leighton 1994-1998 + Copyright (C) Jeremy Allison 1994-1998 + + 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" + +/* This is our local master browser list database. */ +extern ubi_dlList lmb_browserlist[]; + +/**************************************************************************** +As a domain master browser, do a sync with a local master browser. +**************************************************************************/ +static void sync_with_lmb(struct browse_cache_record *browc) +{ + struct work_record *work; + + if( !(work = find_workgroup_on_subnet(unicast_subnet, browc->work_group)) ) + { + if( DEBUGLVL( 0 ) ) + { + dbgtext( "sync_with_lmb:\n" ); + dbgtext( "Failed to get a workgroup for a local master browser " ); + dbgtext( "cache entry workgroup " ); + dbgtext( "%s, server %s\n", browc->work_group, browc->lmb_name ); + } + return; + } + + /* We should only be doing this if we are a domain master browser for + the given workgroup. Ensure this is so. */ + + if(!AM_DOMAIN_MASTER_BROWSER(work)) + { + if( DEBUGLVL( 0 ) ) + { + dbgtext( "sync_with_lmb:\n" ); + dbgtext( "We are trying to sync with a local master browser " ); + dbgtext( "%s for workgroup %s\n", browc->lmb_name, browc->work_group ); + dbgtext( "and we are not a domain master browser on this workgroup.\n" ); + dbgtext( "Error!\n" ); + } + return; + } + + if( DEBUGLVL( 2 ) ) + { + dbgtext( "sync_with_lmb:\n" ); + dbgtext( "Initiating sync with local master browser " ); + dbgtext( "%s<0x20> at IP %s ", browc->lmb_name, inet_ntoa(browc->ip) ); + dbgtext( "for workgroup %s\n", browc->work_group ); + } + + sync_browse_lists(work, browc->lmb_name, 0x20, browc->ip, True, True); + + browc->sync_time += (CHECK_TIME_DMB_TO_LMB_SYNC * 60); +} + +/**************************************************************************** +Sync or expire any local master browsers. +**************************************************************************/ +void dmb_expire_and_sync_browser_lists(time_t t) +{ + static time_t last_run = 0; + struct browse_cache_record *browc; + + /* Only do this every 20 seconds. */ + if (t - last_run < 20) + return; + + last_run = t; + + expire_lmb_browsers(t); + + for( browc = (struct browse_cache_record *)ubi_dlFirst( lmb_browserlist ); + browc; + browc = (struct browse_cache_record *)ubi_dlNext( browc ) ) + { + if (browc->sync_time < t) + sync_with_lmb(browc); + } +} + +/**************************************************************************** +As a local master browser, send an announce packet to the domain master browser. +**************************************************************************/ + +static void announce_local_master_browser_to_domain_master_browser( struct work_record *work) +{ + pstring outbuf; + char *p; + + if(ismyip(work->dmb_addr)) + { + if( DEBUGLVL( 2 ) ) + { + dbgtext( "announce_local_master_browser_to_domain_master_browser:\n" ); + dbgtext( "We are both a domain and a local master browser for " ); + dbgtext( "workgroup %s. ", work->work_group ); + dbgtext( "Do not announce to ourselves.\n" ); + } + return; + } + + memset(outbuf,'\0',sizeof(outbuf)); + p = outbuf; + SCVAL(p,0,ANN_MasterAnnouncement); + p++; + + StrnCpy(p,lp_netbios_name(),15); + strupper(p); + p = skip_string(p,1); + + if( DEBUGLVL( 4 ) ) + { + dbgtext( "announce_local_master_browser_to_domain_master_browser:\n" ); + dbgtext( "Sending local master announce to " ); + dbgtext( "%s for workgroup %s.\n", nmb_namestr(&work->dmb_name), + work->work_group ); + } + + send_mailslot(True, BROWSE_MAILSLOT, outbuf,PTR_DIFF(p,outbuf), + lp_netbios_name(), 0x0, work->dmb_name.name, 0x0, + work->dmb_addr, FIRST_SUBNET->myip, DGRAM_PORT); + +} + +/**************************************************************************** +As a local master browser, do a sync with a domain master browser. +**************************************************************************/ + +static void sync_with_dmb(struct work_record *work) +{ + if( DEBUGLVL( 2 ) ) + { + dbgtext( "sync_with_dmb:\n" ); + dbgtext( "Initiating sync with domain master browser " ); + dbgtext( "%s ", nmb_namestr(&work->dmb_name) ); + dbgtext( "at IP %s ", inet_ntoa(work->dmb_addr) ); + dbgtext( "for workgroup %s\n", work->work_group ); + } + + sync_browse_lists(work, work->dmb_name.name, work->dmb_name.name_type, + work->dmb_addr, False, True); +} + +/**************************************************************************** + Function called when a node status query to a domain master browser IP succeeds. +****************************************************************************/ + +static void domain_master_node_status_success(struct subnet_record *subrec, + struct userdata_struct *userdata, + struct res_rec *answers, + struct in_addr from_ip) +{ + struct work_record *work = find_workgroup_on_subnet( subrec, userdata->data); + + if( work == NULL ) + { + if( DEBUGLVL( 0 ) ) + { + dbgtext( "domain_master_node_status_success:\n" ); + dbgtext( "Unable to find workgroup " ); + dbgtext( "%s on subnet %s.\n", userdata->data, subrec->subnet_name ); + } + return; + } + + if( DEBUGLVL( 3 ) ) + { + dbgtext( "domain_master_node_status_success:\n" ); + dbgtext( "Success in node status for workgroup " ); + dbgtext( "%s from ip %s\n", work->work_group, inet_ntoa(from_ip) ); + } + + /* Go through the list of names found at answers->rdata and look for + the first SERVER<0x20> name. */ + + if(answers->rdata != NULL) + { + char *p = answers->rdata; + int numnames = CVAL(p, 0); + + p += 1; + + while (numnames--) + { + char qname[17]; + uint16 nb_flags; + int name_type; + + StrnCpy(qname,p,15); + name_type = CVAL(p,15); + nb_flags = get_nb_flags(&p[16]); + trim_string(qname,NULL," "); + + p += 18; + + if(!(nb_flags & NB_GROUP) && (name_type == 0x20)) + { + struct nmb_name nmbname; + + make_nmb_name(&nmbname, qname, name_type); + + /* Copy the dmb name and IP address + into the workgroup struct. */ + + work->dmb_name = nmbname; + putip((char *)&work->dmb_addr, &from_ip); + + /* Do the local master browser announcement to the domain + master browser name and IP. */ + announce_local_master_browser_to_domain_master_browser( work ); + + /* Now synchronise lists with the domain master browser. */ + sync_with_dmb(work); + break; + } + } + } + else + if( DEBUGLVL( 0 ) ) + { + dbgtext( "domain_master_node_status_success:\n" ); + dbgtext( "Failed to find a SERVER<0x20> name in reply from IP " ); + dbgtext( "%s.\n", inet_ntoa(from_ip) ); + } +} + +/**************************************************************************** + Function called when a node status query to a domain master browser IP fails. +****************************************************************************/ + +static void domain_master_node_status_fail(struct subnet_record *subrec, + struct response_record *rrec) +{ + struct userdata_struct *userdata = rrec->userdata; + + if( DEBUGLVL( 0 ) ) + { + dbgtext( "domain_master_node_status_fail:\n" ); + dbgtext( "Doing a node status request to the domain master browser\n" ); + dbgtext( "for workgroup %s ", userdata->data ); + dbgtext( "at IP %s failed.\n", inet_ntoa(rrec->packet->ip) ); + dbgtext( "Cannot sync browser lists.\n" ); + } +} + +/**************************************************************************** + Function called when a query for a WORKGROUP<1b> name succeeds. +****************************************************************************/ + +static void find_domain_master_name_query_success(struct subnet_record *subrec, + struct userdata_struct *userdata_in, + struct nmb_name *q_name, struct in_addr answer_ip, struct res_rec *rrec) +{ + /* + * Unfortunately, finding the IP address of the Domain Master Browser, + * as we have here, is not enough. We need to now do a sync to the + * SERVERNAME<0x20> NetBIOS name, as only recent NT servers will + * respond to the SMBSERVER name. To get this name from IP + * address we do a Node status request, and look for the first + * NAME<0x20> in the response, and take that as the server name. + * We also keep a cache of the Domain Master Browser name for this + * workgroup in the Workgroup struct, so that if the same IP addess + * is returned every time, we don't need to do the node status + * request. + */ + + struct work_record *work; + struct nmb_name nmbname; + struct userdata_struct *userdata; + int size = sizeof(struct userdata_struct) + sizeof(fstring)+1; + + if( !(work = find_workgroup_on_subnet(subrec, q_name->name)) ) + { + if( DEBUGLVL( 0 ) ) + { + dbgtext( "find_domain_master_name_query_success:\n" ); + dbgtext( "Failed to find workgroup %s\n", q_name->name ); + } + return; + } + + /* First check if we already have a dmb for this workgroup. */ + + if(!is_zero_ip(work->dmb_addr) && ip_equal(work->dmb_addr, answer_ip)) + { + /* Do the local master browser announcement to the domain + master browser name and IP. */ + announce_local_master_browser_to_domain_master_browser( work ); + + /* Now synchronise lists with the domain master browser. */ + sync_with_dmb(work); + return; + } + else + zero_ip(&work->dmb_addr); + + /* Now initiate the node status request. */ + make_nmb_name(&nmbname,"*",0x0); + + /* Put the workgroup name into the userdata so we know + what workgroup we're talking to when the reply comes + back. */ + + /* Setup the userdata_struct - this is copied so we can use + a stack variable for this. */ + if((userdata = (struct userdata_struct *)malloc(size)) == NULL) + { + DEBUG(0, ("find_domain_master_name_query_success: malloc fail.\n")); + return; + } + + userdata->copy_fn = NULL; + userdata->free_fn = NULL; + userdata->userdata_len = strlen(work->work_group)+1; + fstrcpy(userdata->data, work->work_group); + + node_status( subrec, &nmbname, answer_ip, + domain_master_node_status_success, + domain_master_node_status_fail, + userdata); + + zero_free(userdata, size); +} + +/**************************************************************************** + Function called when a query for a WORKGROUP<1b> name fails. + ****************************************************************************/ +static void find_domain_master_name_query_fail(struct subnet_record *subrec, + struct response_record *rrec, + struct nmb_name *question_name, int fail_code) +{ + if( DEBUGLVL( 0 ) ) + { + dbgtext( "find_domain_master_name_query_fail:\n" ); + dbgtext( "Unable to find the Domain Master Browser name " ); + dbgtext( "%s for the workgroup %s.\n", + nmb_namestr(question_name), question_name->name ); + dbgtext( "Unable to sync browse lists in this workgroup.\n" ); + } +} + +/**************************************************************************** +As a local master browser for a workgroup find the domain master browser +name, announce ourselves as local master browser to it and then pull the +full domain browse lists from it onto the given subnet. +**************************************************************************/ + +void announce_and_sync_with_domain_master_browser( struct subnet_record *subrec, + struct work_record *work) +{ + struct nmb_name nmbname; + + /* Only do this if we are using a WINS server. */ + if(we_are_a_wins_client() == False) + { + if( DEBUGLVL( 10 ) ) + { + dbgtext( "announce_and_sync_with_domain_master_browser:\n" ); + dbgtext( "Ignoring, as we are not a WINS client.\n" ); + } + return; + } + + make_nmb_name(&nmbname,work->work_group,0x1b); + + /* First, query for the WORKGROUP<1b> name from the WINS server. */ + query_name(unicast_subnet, nmbname.name, nmbname.name_type, + find_domain_master_name_query_success, + find_domain_master_name_query_fail, + NULL); + +} + +/**************************************************************************** + Function called when a node status query to a domain master browser IP succeeds. + This function is only called on query to a Samba 1.9.18 or above WINS server. + + Note that adding the workgroup name is enough for this workgroup to be + browsable by clients, as clients query the WINS server or broadcast + nets for the WORKGROUP<1b> name when they want to browse a workgroup + they are not in. We do not need to do a sync with this Domain Master + Browser in order for our browse clients to see machines in this workgroup. + JRA. +****************************************************************************/ + +static void get_domain_master_name_node_status_success(struct subnet_record *subrec, + struct userdata_struct *userdata, + struct res_rec *answers, + struct in_addr from_ip) +{ + struct work_record *work; + fstring server_name; + + server_name[0] = 0; + + if( DEBUGLVL( 3 ) ) + { + dbgtext( "get_domain_master_name_node_status_success:\n" ); + dbgtext( "Success in node status from ip %s\n", inet_ntoa(from_ip) ); + } + + /* + * Go through the list of names found at answers->rdata and look for + * the first WORKGROUP<0x1b> name. + */ + + if(answers->rdata != NULL) + { + char *p = answers->rdata; + int numnames = CVAL(p, 0); + + p += 1; + + while (numnames--) + { + char qname[17]; + uint16 nb_flags; + int name_type; + + StrnCpy(qname,p,15); + name_type = CVAL(p,15); + nb_flags = get_nb_flags(&p[16]); + trim_string(qname,NULL," "); + + p += 18; + + if(!(nb_flags & NB_GROUP) && (name_type == 0x00) && + server_name[0] == 0) { + /* this is almost certainly the server netbios name */ + fstrcpy(server_name, qname); + continue; + } + + if(!(nb_flags & NB_GROUP) && (name_type == 0x1b)) + { + if( DEBUGLVL( 5 ) ) + { + dbgtext( "get_domain_master_name_node_status_success:\n" ); + dbgtext( "%s(%s) ", server_name, inet_ntoa(from_ip) ); + dbgtext( "is a domain master browser for workgroup " ); + dbgtext( "%s. Adding this name.\n", qname ); + } + + /* + * If we don't already know about this workgroup, add it + * to the workgroup list on the unicast_subnet. + */ + if((work = find_workgroup_on_subnet( subrec, qname)) == NULL) + { + struct nmb_name nmbname; + /* + * Add it - with an hour in the cache. + */ + if(!(work= create_workgroup_on_subnet(subrec, qname, 60*60))) + return; + + /* remember who the master is */ + fstrcpy(work->local_master_browser_name, server_name); + make_nmb_name(&nmbname, server_name, 0x20); + work->dmb_name = nmbname; + work->dmb_addr = from_ip; + } + break; + } + } + } + else + if( DEBUGLVL( 0 ) ) + { + dbgtext( "get_domain_master_name_node_status_success:\n" ); + dbgtext( "Failed to find a WORKGROUP<0x1b> name in reply from IP " ); + dbgtext( "%s.\n", inet_ntoa(from_ip) ); + } +} + +/**************************************************************************** + Function called when a node status query to a domain master browser IP fails. +****************************************************************************/ + +static void get_domain_master_name_node_status_fail(struct subnet_record *subrec, + struct response_record *rrec) +{ + if( DEBUGLVL( 0 ) ) + { + dbgtext( "get_domain_master_name_node_status_fail:\n" ); + dbgtext( "Doing a node status request to the domain master browser " ); + dbgtext( "at IP %s failed.\n", inet_ntoa(rrec->packet->ip) ); + dbgtext( "Cannot get workgroup name.\n" ); + } +} + +/**************************************************************************** + Function called when a query for *<1b> name succeeds. +****************************************************************************/ + +static void find_all_domain_master_names_query_success(struct subnet_record *subrec, + struct userdata_struct *userdata_in, + struct nmb_name *q_name, struct in_addr answer_ip, struct res_rec *rrec) +{ + /* + * We now have a list of all the domain master browsers for all workgroups + * that have registered with the WINS server. Now do a node status request + * to each one and look for the first 1b name in the reply. This will be + * the workgroup name that we will add to the unicast subnet as a 'non-local' + * workgroup. + */ + + struct nmb_name nmbname; + struct in_addr send_ip; + int i; + + if( DEBUGLVL( 5 ) ) + { + dbgtext( "find_all_domain_master_names_query_succes:\n" ); + dbgtext( "Got answer from WINS server of %d ", (rrec->rdlength / 6) ); + dbgtext( "IP addresses for Domain Master Browsers.\n" ); + } + + for(i = 0; i < rrec->rdlength / 6; i++) + { + /* Initiate the node status requests. */ + make_nmb_name(&nmbname, "*", 0); + + putip((char *)&send_ip, (char *)&rrec->rdata[(i*6) + 2]); + + /* + * Don't send node status requests to ourself. + */ + + if(ismyip( send_ip )) + { + if( DEBUGLVL( 5 ) ) + { + dbgtext( "find_all_domain_master_names_query_succes:\n" ); + dbgtext( "Not sending node status to our own IP " ); + dbgtext( "%s.\n", inet_ntoa(send_ip) ); + } + continue; + } + + if( DEBUGLVL( 5 ) ) + { + dbgtext( "find_all_domain_master_names_query_success:\n" ); + dbgtext( "Sending node status request to IP %s.\n", inet_ntoa(send_ip) ); + } + + node_status( subrec, &nmbname, send_ip, + get_domain_master_name_node_status_success, + get_domain_master_name_node_status_fail, + NULL); + } +} + +/**************************************************************************** + Function called when a query for *<1b> name fails. + ****************************************************************************/ +static void find_all_domain_master_names_query_fail(struct subnet_record *subrec, + struct response_record *rrec, + struct nmb_name *question_name, int fail_code) +{ + if( DEBUGLVL( 10 ) ) + { + dbgtext( "find_domain_master_name_query_fail:\n" ); + dbgtext( "WINS server did not reply to a query for name " ); + dbgtext( "%s.\nThis means it ", nmb_namestr(question_name) ); + dbgtext( "is probably not a Samba 1.9.18 or above WINS server.\n" ); + } +} + +/**************************************************************************** + If we are a domain master browser on the unicast subnet, do a query to the + WINS server for the *<1b> name. This will only work to a Samba WINS server, + so ignore it if we fail. If we succeed, contact each of the IP addresses in + turn and do a node status request to them. If this succeeds then look for a + <1b> name in the reply - this is the workgroup name. Add this to the unicast + subnet. This is expensive, so we only do this every 15 minutes. +**************************************************************************/ +void collect_all_workgroup_names_from_wins_server(time_t t) +{ + static time_t lastrun = 0; + struct work_record *work; + struct nmb_name nmbname; + + /* Only do this if we are using a WINS server. */ + if(we_are_a_wins_client() == False) + return; + + /* Check to see if we are a domain master browser on the unicast subnet. */ + if((work = find_workgroup_on_subnet( unicast_subnet, lp_workgroup())) == NULL) + { + if( DEBUGLVL( 0 ) ) + { + dbgtext( "collect_all_workgroup_names_from_wins_server:\n" ); + dbgtext( "Cannot find my workgroup %s ", lp_workgroup() ); + dbgtext( "on subnet %s.\n", unicast_subnet->subnet_name ); + } + return; + } + + if(!AM_DOMAIN_MASTER_BROWSER(work)) + return; + + if ((lastrun != 0) && (t < lastrun + (15 * 60))) + return; + + lastrun = t; + + make_nmb_name(&nmbname,"*",0x1b); + + /* First, query for the *<1b> name from the WINS server. */ + query_name(unicast_subnet, nmbname.name, nmbname.name_type, + find_all_domain_master_names_query_success, + find_all_domain_master_names_query_fail, + NULL); +} + + +/**************************************************************************** + If we are a domain master browser on the unicast subnet, do a regular sync + with all other DMBs that we know of on that subnet. + +To prevent exponential network traffic with large numbers of workgroups +we use a randomised system where sync probability is inversely proportional +to the number of known workgroups +**************************************************************************/ +void sync_all_dmbs(time_t t) +{ + static time_t lastrun = 0; + struct work_record *work; + int count=0; + + /* Only do this if we are using a WINS server. */ + if(we_are_a_wins_client() == False) + return; + + /* Check to see if we are a domain master browser on the + unicast subnet. */ + work = find_workgroup_on_subnet(unicast_subnet, lp_workgroup()); + if (!work) return; + + if (!AM_DOMAIN_MASTER_BROWSER(work)) + return; + + if ((lastrun != 0) && (t < lastrun + (5 * 60))) + return; + + /* count how many syncs we might need to do */ + for (work=unicast_subnet->workgrouplist; work; work = work->next) { + if (strcmp(lp_workgroup(), work->work_group)) { + count++; + } + } + + /* sync with a probability of 1/count */ + for (work=unicast_subnet->workgrouplist; work; work = work->next) { + if (strcmp(lp_workgroup(), work->work_group)) { + if (((unsigned)sys_random()) % count != 0) continue; + + lastrun = t; + + if (!work->dmb_name.name[0]) { + /* we don't know the DMB - assume it is + the same as the unicast local master */ + make_nmb_name(&work->dmb_name, + work->local_master_browser_name, + 0x20); + } + + DEBUG(3,("Initiating DMB<->DMB sync with %s(%s)\n", + work->dmb_name.name, + inet_ntoa(work->dmb_addr))); + sync_browse_lists(work, + work->dmb_name.name, + work->dmb_name.name_type, + work->dmb_addr, False, False); + } + } +} diff --git a/source4/nmbd/nmbd_elections.c b/source4/nmbd/nmbd_elections.c new file mode 100644 index 0000000000..759379cc84 --- /dev/null +++ b/source4/nmbd/nmbd_elections.c @@ -0,0 +1,403 @@ +/* + Unix SMB/CIFS implementation. + NBT netbios routines and daemon - version 2 + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Luke Kenneth Casson Leighton 1994-1998 + Copyright (C) Jeremy Allison 1994-1998 + + 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" + +/* Election parameters. */ +extern time_t StartupTime; + +/**************************************************************************** + Send an election datagram packet. +**************************************************************************/ +static void send_election_dgram(struct subnet_record *subrec, const char *workgroup_name, + uint32 criterion, int timeup,const char *server_name) +{ + pstring outbuf; + char *p; + + DEBUG(2,("send_election_dgram: Sending election packet for workgroup %s on subnet %s\n", + workgroup_name, subrec->subnet_name )); + + memset(outbuf,'\0',sizeof(outbuf)); + p = outbuf; + SCVAL(p,0,ANN_Election); /* Election opcode. */ + p++; + + SCVAL(p,0,((criterion == 0 && timeup == 0) ? 0 : ELECTION_VERSION)); + SIVAL(p,1,criterion); + SIVAL(p,5,timeup*1000); /* ms - Despite what the spec says. */ + p += 13; + pstrcpy(p,server_name); + strupper(p); + p = skip_string(p,1); + + send_mailslot(False, BROWSE_MAILSLOT, outbuf, PTR_DIFF(p,outbuf), + lp_netbios_name(), 0, + workgroup_name, 0x1e, + subrec->bcast_ip, subrec->myip, DGRAM_PORT); +} + +/******************************************************************* + We found a current master browser on one of our broadcast interfaces. +******************************************************************/ + +static void check_for_master_browser_success(struct subnet_record *subrec, + struct userdata_struct *userdata, + struct nmb_name *answer_name, + struct in_addr answer_ip, struct res_rec *rrec) +{ + DEBUG(3,("check_for_master_browser_success: Local master browser for workgroup %s exists at \ +IP %s (just checking).\n", answer_name->name, inet_ntoa(answer_ip) )); +} + +/******************************************************************* + We failed to find a current master browser on one of our broadcast interfaces. +******************************************************************/ + +static void check_for_master_browser_fail( struct subnet_record *subrec, + struct response_record *rrec, + struct nmb_name *question_name, + int fail_code) +{ + char *workgroup_name = question_name->name; + struct work_record *work = find_workgroup_on_subnet(subrec, workgroup_name); + + if(work == NULL) + { + DEBUG(0,("check_for_master_browser_fail: Unable to find workgroup %s on subnet %s.=\n", + workgroup_name, subrec->subnet_name )); + return; + } + + if (strequal(work->work_group, lp_workgroup())) + { + + if (lp_local_master()) + { + /* We have discovered that there is no local master + browser, and we are configured to initiate + an election that we will participate in. + */ + DEBUG(2,("check_for_master_browser_fail: Forcing election on workgroup %s subnet %s\n", + work->work_group, subrec->subnet_name )); + + /* Setting this means we will participate when the + election is run in run_elections(). */ + work->needelection = True; + } + else + { + /* We need to force an election, because we are configured + not to become the local master, but we still need one, + having detected that one doesn't exist. + */ + send_election_dgram(subrec, work->work_group, 0, 0, ""); + } + } +} + +/******************************************************************* + Ensure there is a local master browser for a workgroup on our + broadcast interfaces. +******************************************************************/ + +void check_master_browser_exists(time_t t) +{ + static time_t lastrun=0; + struct subnet_record *subrec; + const char *workgroup_name = lp_workgroup(); + + if (!lastrun) + lastrun = t; + + if (t < (lastrun + (CHECK_TIME_MST_BROWSE * 60))) + return; + + lastrun = t; + + dump_workgroups(False); + + for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) + { + struct work_record *work; + + for (work = subrec->workgrouplist; work; work = work->next) + { + if (strequal(work->work_group, workgroup_name) && !AM_LOCAL_MASTER_BROWSER(work)) + { + /* Do a name query for the local master browser on this net. */ + query_name( subrec, work->work_group, 0x1d, + check_for_master_browser_success, + check_for_master_browser_fail, + NULL); + } + } + } +} + +/******************************************************************* + Run an election. +******************************************************************/ + +void run_elections(time_t t) +{ + static time_t lastime = 0; + + struct subnet_record *subrec; + + /* Send election packets once every 2 seconds - note */ + if (lastime && (t - lastime < 2)) + return; + + lastime = t; + + START_PROFILE(run_elections); + for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) + { + struct work_record *work; + + for (work = subrec->workgrouplist; work; work = work->next) + { + if (work->RunningElection) + { + /* + * We can only run an election for a workgroup if we have + * registered the WORKGROUP<1e> name, as that's the name + * we must listen to. + */ + struct nmb_name nmbname; + + make_nmb_name(&nmbname, work->work_group, 0x1e); + if(find_name_on_subnet( subrec, &nmbname, FIND_SELF_NAME)==NULL) { + DEBUG(8,("run_elections: Cannot send election packet yet as name %s not \ +yet registered on subnet %s\n", nmb_namestr(&nmbname), subrec->subnet_name )); + continue; + } + + send_election_dgram(subrec, work->work_group, work->ElectionCriterion, + t - StartupTime, lp_netbios_name()); + + if (work->ElectionCount++ >= 4) + { + /* Won election (4 packets were sent out uncontested. */ + DEBUG(2,("run_elections: >>> Won election for workgroup %s on subnet %s <<<\n", + work->work_group, subrec->subnet_name )); + + work->RunningElection = False; + + become_local_master_browser(subrec, work); + } + } + } + } + END_PROFILE(run_elections); +} + +/******************************************************************* + Determine if I win an election. +******************************************************************/ + +static BOOL win_election(struct work_record *work, int version, + uint32 criterion, int timeup, char *server_name) +{ + int mytimeup = time(NULL) - StartupTime; + uint32 mycriterion = work->ElectionCriterion; + + /* If local master is false then never win + in election broadcasts. */ + if(!lp_local_master()) + { + DEBUG(3,("win_election: Losing election as local master == False\n")); + return False; + } + + DEBUG(4,("win_election: election comparison: %x:%x %x:%x %d:%d %s:%s\n", + version, ELECTION_VERSION, + criterion, mycriterion, + timeup, mytimeup, + server_name, lp_netbios_name())); + + if (version > ELECTION_VERSION) + return(False); + if (version < ELECTION_VERSION) + return(True); + + if (criterion > mycriterion) + return(False); + if (criterion < mycriterion) + return(True); + + if (timeup > mytimeup) + return(False); + if (timeup < mytimeup) + return(True); + + if (strcasecmp(lp_netbios_name(), server_name) > 0) + return(False); + + return(True); +} + +/******************************************************************* + Process an incoming election datagram packet. +******************************************************************/ + +void process_election(struct subnet_record *subrec, struct packet_struct *p, char *buf) +{ + struct dgram_packet *dgram = &p->packet.dgram; + int version = CVAL(buf,0); + uint32 criterion = IVAL(buf,1); + int timeup = IVAL(buf,5)/1000; + char *server_name = buf+13; + struct work_record *work; + char *workgroup_name = dgram->dest_name.name; + + START_PROFILE(election); + server_name[15] = 0; + + DEBUG(3,("process_election: Election request from %s at IP %s on subnet %s for workgroup %s.\n", + server_name,inet_ntoa(p->ip), subrec->subnet_name, workgroup_name )); + + DEBUG(5,("process_election: vers=%d criterion=%08x timeup=%d\n", version,criterion,timeup)); + + if(( work = find_workgroup_on_subnet(subrec, workgroup_name)) == NULL) + { + DEBUG(0,("process_election: Cannot find workgroup %s on subnet %s.\n", + workgroup_name, subrec->subnet_name )); + goto done; + } + + if (!strequal(work->work_group, lp_workgroup())) + { + DEBUG(3,("process_election: ignoring election request for workgroup %s on subnet %s as this \ +is not my workgroup.\n", work->work_group, subrec->subnet_name )); + goto done; + } + + if (win_election(work, version,criterion,timeup,server_name)) + { + /* We take precedence over the requesting server. */ + if (!work->RunningElection) + { + /* We weren't running an election - start running one. */ + + work->needelection = True; + work->ElectionCount=0; + } + + /* Note that if we were running an election for this workgroup on this + subnet already, we just ignore the server we take precedence over. */ + } + else + { + /* We lost. Stop participating. */ + work->needelection = False; + + if (work->RunningElection || AM_LOCAL_MASTER_BROWSER(work)) + { + work->RunningElection = False; + DEBUG(3,("process_election: >>> Lost election for workgroup %s on subnet %s <<<\n", + work->work_group, subrec->subnet_name )); + if (AM_LOCAL_MASTER_BROWSER(work)) + unbecome_local_master_browser(subrec, work, False); + } + } +done: + END_PROFILE(election); +} + +/**************************************************************************** + This function looks over all the workgroups known on all the broadcast + subnets and decides if a browser election is to be run on that workgroup. + It returns True if any election packets need to be sent (this will then + be done by run_elections(). +***************************************************************************/ + +BOOL check_elections(void) +{ + struct subnet_record *subrec; + BOOL run_any_election = False; + + for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) + { + struct work_record *work; + for (work = subrec->workgrouplist; work; work = work->next) + { + run_any_election |= work->RunningElection; + + /* + * Start an election if we have any chance of winning. + * Note this is a change to the previous code, that would + * only run an election if nmbd was in the potential browser + * state. We need to run elections in any state if we're told + * to. JRA. + */ + + if (work->needelection && !work->RunningElection && lp_local_master()) + { + /* + * We can only run an election for a workgroup if we have + * registered the WORKGROUP<1e> name, as that's the name + * we must listen to. + */ + struct nmb_name nmbname; + + make_nmb_name(&nmbname, work->work_group, 0x1e); + if(find_name_on_subnet( subrec, &nmbname, FIND_SELF_NAME)==NULL) { + DEBUG(8,("check_elections: Cannot send election packet yet as name %s not \ +yet registered on subnet %s\n", nmb_namestr(&nmbname), subrec->subnet_name )); + continue; + } + + DEBUG(3,("check_elections: >>> Starting election for workgroup %s on subnet %s <<<\n", + work->work_group, subrec->subnet_name )); + + work->ElectionCount = 0; + work->RunningElection = True; + work->needelection = False; + } + } + } + return run_any_election; +} + + + +/**************************************************************************** +process a internal Samba message forcing an election +***************************************************************************/ +void nmbd_message_election(int msg_type, pid_t src, void *buf, size_t len) +{ + struct subnet_record *subrec; + + for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) { + struct work_record *work; + for (work = subrec->workgrouplist; work; work = work->next) { + if (strequal(work->work_group, lp_workgroup())) { + work->needelection = True; + work->ElectionCount=0; + work->mst_state = lp_local_master() ? MST_POTENTIAL : MST_NONE; + } + } + } +} diff --git a/source4/nmbd/nmbd_incomingdgrams.c b/source4/nmbd/nmbd_incomingdgrams.c new file mode 100644 index 0000000000..3000c347d8 --- /dev/null +++ b/source4/nmbd/nmbd_incomingdgrams.c @@ -0,0 +1,858 @@ +/* + Unix SMB/CIFS implementation. + NBT netbios routines and daemon - version 2 + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Luke Kenneth Casson Leighton 1994-1998 + Copyright (C) Jeremy Allison 1994-1998 + + 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" + +extern BOOL found_lm_clients; + +#if 0 + +/* XXXX note: This function is currently unsuitable for use, as it + does not properly check that a server is in a fit state to become + a backup browser before asking it to be one. + The code is left here to be worked on at a later date. +*/ + +/**************************************************************************** +Tell a server to become a backup browser +**************************************************************************/ + +void tell_become_backup(void) +{ + struct subnet_record *subrec; + for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) + { + struct work_record *work; + for (work = subrec->workgrouplist; work; work = work->next) + { + struct server_record *servrec; + int num_servers = 0; + int num_backups = 0; + + for (servrec = work->serverlist; servrec; servrec = servrec->next) + { + num_servers++; + + if (is_myname(servrec->serv.name)) + continue; + + if (servrec->serv.type & SV_TYPE_BACKUP_BROWSER) + { + num_backups++; + continue; + } + + if (servrec->serv.type & SV_TYPE_MASTER_BROWSER) + continue; + + if (!(servrec->serv.type & SV_TYPE_POTENTIAL_BROWSER)) + continue; + + DEBUG(3,("num servers: %d num backups: %d\n", + num_servers, num_backups)); + + /* make first server a backup server. thereafter make every + tenth server a backup server */ + if (num_backups != 0 && (num_servers+9) / num_backups > 10) + continue; + + DEBUG(2,("sending become backup to %s %s for %s\n", + servrec->serv.name, inet_ntoa(subrec->bcast_ip), + work->work_group)); + + /* type 11 request from MYNAME(20) to WG(1e) for SERVER */ + do_announce_request(servrec->serv.name, work->work_group, + ANN_BecomeBackup, 0x20, 0x1e, subrec->bcast_ip); + } + } + } +} +#endif + +/******************************************************************* + Process an incoming host announcement packet. +*******************************************************************/ + +void process_host_announce(struct subnet_record *subrec, struct packet_struct *p, char *buf) +{ + struct dgram_packet *dgram = &p->packet.dgram; + int ttl = IVAL(buf,1)/1000; + char *announce_name = buf+5; + uint32 servertype = IVAL(buf,23); + char *comment = buf+31; + struct work_record *work; + struct server_record *servrec; + const char *work_name; + char *source_name = dgram->source_name.name; + + START_PROFILE(host_announce); + comment[43] = 0; + + DEBUG(3,("process_host_announce: from %s<%02x> IP %s to \ +%s for server %s.\n", source_name, source_name[15], inet_ntoa(p->ip), + nmb_namestr(&dgram->dest_name),announce_name)); + + DEBUG(5,("process_host_announce: ttl=%d server type=%08x comment=%s\n", + ttl, servertype,comment)); + + /* Filter servertype to remove impossible bits. */ + servertype &= ~(SV_TYPE_LOCAL_LIST_ONLY|SV_TYPE_DOMAIN_ENUM); + + /* A host announcement must be sent to the name WORKGROUP<1d>. */ + if(dgram->dest_name.name_type != 0x1d) + { + DEBUG(2,("process_host_announce: incorrect name type for destination from IP %s \ +(was %02x) should be 0x1d. Allowing packet anyway.\n", + inet_ntoa(p->ip), dgram->dest_name.name_type)); + /* Change it so it was. */ + dgram->dest_name.name_type = 0x1d; + } + + /* For a host announce the workgroup name is the destination name. */ + work_name = dgram->dest_name.name; + + /* + * Syntax servers version 5.1 send HostAnnounce packets to + * *THE WRONG NAME*. They send to LOCAL_MASTER_BROWSER_NAME<00> + * instead of WORKGROUP<1d> name. So to fix this we check if + * the workgroup name is our own name, and if so change it + * to be our primary workgroup name. + */ + + if(strequal(work_name, lp_netbios_name())) + work_name = lp_workgroup(); + + /* + * We are being very agressive here in adding a workgroup + * name on the basis of a host announcing itself as being + * in that workgroup. Maybe we should wait for the workgroup + * announce instead ? JRA. + */ + + work = find_workgroup_on_subnet(subrec, work_name); + + if(servertype != 0) + { + if (work ==NULL ) + { + /* We have no record of this workgroup. Add it. */ + if((work = create_workgroup_on_subnet(subrec, work_name, ttl))==NULL) + goto done; + } + + if((servrec = find_server_in_workgroup( work, announce_name))==NULL) + { + /* If this server is not already in the workgroup, add it. */ + create_server_on_workgroup(work, announce_name, + servertype|SV_TYPE_LOCAL_LIST_ONLY, + ttl, comment); + } + else + { + /* Update the record. */ + servrec->serv.type = servertype|SV_TYPE_LOCAL_LIST_ONLY; + update_server_ttl( servrec, ttl); + StrnCpy(servrec->serv.comment,comment,sizeof(servrec->serv.comment)-1); + } + } + else + { + /* + * This server is announcing it is going down. Remove it from the + * workgroup. + */ + if(!is_myname(announce_name) && (work != NULL) && + ((servrec = find_server_in_workgroup( work, announce_name))!=NULL) + ) + { + remove_server_from_workgroup( work, servrec); + } + } + subrec->work_changed = True; +done: + END_PROFILE(host_announce); +} + +/******************************************************************* + Process an incoming WORKGROUP announcement packet. +*******************************************************************/ + +void process_workgroup_announce(struct subnet_record *subrec, struct packet_struct *p, char *buf) +{ + struct dgram_packet *dgram = &p->packet.dgram; + int ttl = IVAL(buf,1)/1000; + char *workgroup_announce_name = buf+5; + uint32 servertype = IVAL(buf,23); + char *master_name = buf+31; + struct work_record *work; + char *source_name = dgram->source_name.name; + + START_PROFILE(workgroup_announce); + master_name[43] = 0; + + DEBUG(3,("process_workgroup_announce: from %s<%02x> IP %s to \ +%s for workgroup %s.\n", source_name, source_name[15], inet_ntoa(p->ip), + nmb_namestr(&dgram->dest_name),workgroup_announce_name)); + + DEBUG(5,("process_workgroup_announce: ttl=%d server type=%08x master browser=%s\n", + ttl, servertype, master_name)); + + /* Workgroup announcements must only go to the MSBROWSE name. */ + if (!strequal(dgram->dest_name.name, MSBROWSE) || (dgram->dest_name.name_type != 0x1)) + { + DEBUG(0,("process_workgroup_announce: from IP %s should be to __MSBROWSE__<0x01> not %s\n", + inet_ntoa(p->ip), nmb_namestr(&dgram->dest_name))); + goto done; + } + + if ((work = find_workgroup_on_subnet(subrec, workgroup_announce_name))==NULL) + { + /* We have no record of this workgroup. Add it. */ + if((work = create_workgroup_on_subnet(subrec, workgroup_announce_name, ttl))==NULL) + goto done; + } + else + { + /* Update the workgroup death_time. */ + update_workgroup_ttl(work, ttl); + } + + if(*work->local_master_browser_name == '\0') + { + /* Set the master browser name. */ + set_workgroup_local_master_browser_name( work, master_name ); + } + + subrec->work_changed = True; +done: + END_PROFILE(workgroup_announce); +} + +/******************************************************************* + Process an incoming local master browser announcement packet. +*******************************************************************/ + +void process_local_master_announce(struct subnet_record *subrec, struct packet_struct *p, char *buf) +{ + struct dgram_packet *dgram = &p->packet.dgram; + int ttl = IVAL(buf,1)/1000; + char *server_name = buf+5; + uint32 servertype = IVAL(buf,23); + char *comment = buf+31; + char *work_name; + struct work_record *work; + struct server_record *servrec; + char *source_name = dgram->source_name.name; + + START_PROFILE(local_master_announce); + comment[43] = 0; + + DEBUG(3,("process_local_master_announce: from %s<%02x> IP %s to \ +%s for server %s.\n", source_name, source_name[15], inet_ntoa(p->ip), + nmb_namestr(&dgram->dest_name),server_name)); + + DEBUG(5,("process_local_master_announce: ttl=%d server type=%08x comment=%s\n", + ttl, servertype, comment)); + + /* A local master announcement must be sent to the name WORKGROUP<1e>. */ + if(dgram->dest_name.name_type != 0x1e) + { + DEBUG(0,("process_local_master_announce: incorrect name type for destination from IP %s \ +(was %02x) should be 0x1e. Ignoring packet.\n", + inet_ntoa(p->ip), dgram->dest_name.name_type)); + goto done; + } + + /* Filter servertype to remove impossible bits. */ + servertype &= ~(SV_TYPE_LOCAL_LIST_ONLY|SV_TYPE_DOMAIN_ENUM); + + /* For a local master announce the workgroup name is the destination name. */ + work_name = dgram->dest_name.name; + + if ((work = find_workgroup_on_subnet(subrec, work_name))==NULL) + { + /* Don't bother adding if it's a local master release announce. */ + if(servertype == 0) + goto done; + + /* We have no record of this workgroup. Add it. */ + if((work = create_workgroup_on_subnet(subrec, work_name, ttl))==NULL) + goto done; + } + + /* If we think we're the local master browser for this workgroup, + we should never have got this packet. We don't see our own + packets. + */ + if(AM_LOCAL_MASTER_BROWSER(work)) + { + DEBUG(0,("process_local_master_announce: Server %s at IP %s is announcing itself as \ +a local master browser for workgroup %s and we think we are master. Forcing election.\n", + server_name, inet_ntoa(p->ip), work_name)); + + /* Samba nmbd versions 1.9.17 to 1.9.17p4 have a bug in that when + they have become a local master browser once, they will never + stop sending local master announcements. To fix this we send + them a reset browser packet, with level 0x2 on the __SAMBA__ + name that only they should be listening to. */ + + send_browser_reset( 0x2, "__SAMBA__" , 0x20, p->ip); + + /* We should demote ourself and force an election. */ + + unbecome_local_master_browser( subrec, work, True); + + /* The actual election requests are handled in + nmbd_election.c */ + goto done; + } + + /* Find the server record on this workgroup. If it doesn't exist, add it. */ + + if(servertype != 0) + { + if((servrec = find_server_in_workgroup( work, server_name))==NULL) + { + /* If this server is not already in the workgroup, add it. */ + create_server_on_workgroup(work, server_name, + servertype|SV_TYPE_LOCAL_LIST_ONLY, + ttl, comment); + } + else + { + /* Update the record. */ + servrec->serv.type = servertype|SV_TYPE_LOCAL_LIST_ONLY; + update_server_ttl(servrec, ttl); + StrnCpy(servrec->serv.comment,comment,sizeof(servrec->serv.comment)-1); + } + + set_workgroup_local_master_browser_name( work, server_name ); + } + else + { + /* + * This server is announcing it is going down. Remove it from the + * workgroup. + */ + if(!is_myname(server_name) && (work != NULL) && + ((servrec = find_server_in_workgroup( work, server_name))!=NULL) + ) + { + remove_server_from_workgroup( work, servrec); + } + } + + subrec->work_changed = True; +done: + END_PROFILE(local_master_announce); +} + +/******************************************************************* + Process a domain master announcement frame. + Domain master browsers receive these from local masters. The Domain + master should then issue a sync with the local master, asking for + that machines local server list. +******************************************************************/ + +void process_master_browser_announce(struct subnet_record *subrec, + struct packet_struct *p,char *buf) +{ + char *local_master_name = buf; + struct work_record *work; + struct browse_cache_record *browrec; + + START_PROFILE(master_browser_announce); + local_master_name[15] = 0; + + DEBUG(3,("process_master_browser_announce: Local master announce from %s IP %s.\n", + local_master_name, inet_ntoa(p->ip))); + + if (!lp_domain_master()) + { + DEBUG(0,("process_master_browser_announce: Not configured as domain \ +master - ignoring master announce.\n")); + goto done; + } + + if((work = find_workgroup_on_subnet(subrec, lp_workgroup())) == NULL) + { + DEBUG(0,("process_master_browser_announce: Cannot find workgroup %s on subnet %s\n", + lp_workgroup(), subrec->subnet_name)); + goto done; + } + + if(!AM_DOMAIN_MASTER_BROWSER(work)) + { + DEBUG(0,("process_master_browser_announce: Local master announce made to us from \ +%s IP %s and we are not a domain master browser.\n", local_master_name, inet_ntoa(p->ip))); + goto done; + } + + /* Add this host as a local master browser entry on the browse lists. + This causes a sync request to be made to it at a later date. + */ + + if((browrec = find_browser_in_lmb_cache( local_master_name )) == NULL) + { + /* Add it. */ + create_browser_in_lmb_cache( work->work_group, local_master_name, p->ip); + } + else + update_browser_death_time(browrec); +done: + END_PROFILE(master_browser_announce); +} + +/******************************************************************* + Process an incoming LanMan host announcement packet. +*******************************************************************/ + +void process_lm_host_announce(struct subnet_record *subrec, struct packet_struct *p, char *buf) +{ + struct dgram_packet *dgram = &p->packet.dgram; + uint32 servertype = IVAL(buf,1); + int osmajor=CVAL(buf,5); /* major version of node software */ + int osminor=CVAL(buf,6); /* minor version of node software */ + int ttl = SVAL(buf,7); + char *announce_name = buf+9; + struct work_record *work; + struct server_record *servrec; + const char *work_name; + char *source_name = dgram->source_name.name; + pstring comment; + char *s = buf+9; + + START_PROFILE(lm_host_announce); + s = skip_string(s,1); + StrnCpy(comment, s, 43); + + DEBUG(3,("process_lm_host_announce: LM Announcement from %s<%02x> IP %s to \ +%s for server %s.\n", source_name, source_name[15], inet_ntoa(p->ip), + nmb_namestr(&dgram->dest_name),announce_name)); + + DEBUG(5,("process_lm_host_announce: os=(%d,%d) ttl=%d server type=%08x comment=%s\n", + osmajor, osminor, ttl, servertype,comment)); + + if ((osmajor < 36) || (osmajor > 38) || (osminor !=0)) + { + DEBUG(5,("process_lm_host_announce: LM Announcement packet does not \ +originate from OS/2 Warp client. Ignoring packet.\n")); + /* Could have been from a Windows machine (with its LM Announce enabled), + or a Samba server. Then don't disrupt the current browse list. */ + goto done; + } + + /* Filter servertype to remove impossible bits. */ + servertype &= ~(SV_TYPE_LOCAL_LIST_ONLY|SV_TYPE_DOMAIN_ENUM); + + /* A LanMan host announcement must be sent to the name WORKGROUP<00>. */ + if(dgram->dest_name.name_type != 0x00) + { + DEBUG(2,("process_lm_host_announce: incorrect name type for destination from IP %s \ +(was %02x) should be 0x00. Allowing packet anyway.\n", + inet_ntoa(p->ip), dgram->dest_name.name_type)); + /* Change it so it was. */ + dgram->dest_name.name_type = 0x00; + } + + /* For a LanMan host announce the workgroup name is the destination name. */ + work_name = dgram->dest_name.name; + + /* + * Syntax servers version 5.1 send HostAnnounce packets to + * *THE WRONG NAME*. They send to LOCAL_MASTER_BROWSER_NAME<00> + * instead of WORKGROUP<1d> name. So to fix this we check if + * the workgroup name is our own name, and if so change it + * to be our primary workgroup name. This code is probably + * not needed in the LanMan announce code, but it won't hurt. + */ + + if(strequal(work_name, lp_netbios_name())) + work_name = lp_workgroup(); + + /* + * We are being very agressive here in adding a workgroup + * name on the basis of a host announcing itself as being + * in that workgroup. Maybe we should wait for the workgroup + * announce instead ? JRA. + */ + + work = find_workgroup_on_subnet(subrec, work_name); + + if(servertype != 0) + { + if (work == NULL) + { + /* We have no record of this workgroup. Add it. */ + if((work = create_workgroup_on_subnet(subrec, work_name, ttl))==NULL) + goto done; + } + + if((servrec = find_server_in_workgroup( work, announce_name))==NULL) + { + /* If this server is not already in the workgroup, add it. */ + create_server_on_workgroup(work, announce_name, + servertype|SV_TYPE_LOCAL_LIST_ONLY, + ttl, comment); + } + else + { + /* Update the record. */ + servrec->serv.type = servertype|SV_TYPE_LOCAL_LIST_ONLY; + update_server_ttl( servrec, ttl); + StrnCpy(servrec->serv.comment,comment,sizeof(servrec->serv.comment)-1); + } + } + else + { + /* + * This server is announcing it is going down. Remove it from the + * workgroup. + */ + if(!is_myname(announce_name) && (work != NULL) && + ((servrec = find_server_in_workgroup( work, announce_name))!=NULL) + ) + { + remove_server_from_workgroup( work, servrec); + } + } + + subrec->work_changed = True; + found_lm_clients = True; +done: + END_PROFILE(lm_host_announce); +} + +/**************************************************************************** + Send a backup list response. +*****************************************************************************/ +static void send_backup_list_response(struct subnet_record *subrec, + struct work_record *work, + struct nmb_name *send_to_name, + unsigned char max_number_requested, + uint32 token, struct in_addr sendto_ip, + int port) +{ + char outbuf[1024]; + char *p, *countptr; + unsigned int count = 0; +#if 0 + struct server_record *servrec; +#endif + + memset(outbuf,'\0',sizeof(outbuf)); + + DEBUG(3,("send_backup_list_response: sending backup list for workgroup %s to %s IP %s\n", + work->work_group, nmb_namestr(send_to_name), inet_ntoa(sendto_ip))); + + p = outbuf; + + SCVAL(p,0,ANN_GetBackupListResp); /* Backup list response opcode. */ + p++; + + countptr = p; + p++; + + SIVAL(p,0,token); /* The sender's unique info. */ + p += 4; + + /* We always return at least one name - our own. */ + count = 1; + StrnCpy(p,lp_netbios_name(),15); + strupper(p); + p = skip_string(p,1); + + /* Look for backup browsers in this workgroup. */ + +#if 0 + /* we don't currently send become_backup requests so we should never + send any other servers names out as backups for our + workgroup. That's why this is commented out (tridge) */ + + /* + * NB. Note that the struct work_record here is not neccessarily + * attached to the subnet *subrec. + */ + + for (servrec = work->serverlist; servrec; servrec = servrec->next) + { + int len = PTR_DIFF(p, outbuf); + if((sizeof(outbuf) - len) < 16) + break; + + if(count >= (unsigned int)max_number_requested) + break; + + if(strnequal(servrec->serv.name, lp_netbios_name(),15)) + continue; + + if(!(servrec->serv.type & SV_TYPE_BACKUP_BROWSER)) + continue; + + StrnCpy(p, servrec->serv.name, 15); + strupper(p); + count++; + + DEBUG(5,("send_backup_list_response: Adding server %s number %d\n", + p, count)); + + p = skip_string(p,1); + } +#endif + + SCVAL(countptr, 0, count); + + DEBUG(4,("send_backup_list_response: sending response to %s<00> IP %s with %d servers.\n", + send_to_name->name, inet_ntoa(sendto_ip), count)); + + send_mailslot(True, BROWSE_MAILSLOT, + outbuf,PTR_DIFF(p,outbuf), + lp_netbios_name(), 0, + send_to_name->name,0, + sendto_ip, subrec->myip, port); +} + +/******************************************************************* + Process a send backup list request packet. + + A client sends a backup list request to ask for a list of servers on + the net that maintain server lists for a domain. A server is then + chosen from this list to send NetServerEnum commands to to list + available servers. + +********************************************************************/ + +void process_get_backup_list_request(struct subnet_record *subrec, + struct packet_struct *p,char *buf) +{ + struct dgram_packet *dgram = &p->packet.dgram; + struct work_record *work; + unsigned char max_number_requested = CVAL(buf,0); + uint32 token = IVAL(buf,1); /* Sender's key index for the workgroup. */ + int name_type = dgram->dest_name.name_type; + char *workgroup_name = dgram->dest_name.name; + struct subnet_record *search_subrec = subrec; + + START_PROFILE(get_backup_list); + DEBUG(3,("process_get_backup_list_request: request from %s IP %s to %s.\n", + nmb_namestr(&dgram->source_name), inet_ntoa(p->ip), + nmb_namestr(&dgram->dest_name))); + + /* We have to be a master browser, or a domain master browser + for the requested workgroup. That means it must be our + workgroup. */ + + if(strequal(workgroup_name, lp_workgroup()) == False) + { + DEBUG(7,("process_get_backup_list_request: Ignoring announce request for workgroup %s.\n", + workgroup_name)); + goto done; + } + + if((work = find_workgroup_on_subnet(search_subrec, workgroup_name)) == NULL) + { + DEBUG(0,("process_get_backup_list_request: Cannot find workgroup %s on \ +subnet %s.\n", workgroup_name, search_subrec->subnet_name)); + goto done; + } + + /* + * If the packet was sent to WORKGROUP<1b> instead + * of WORKGROUP<1d> then it was unicast to us a domain master + * browser. Change search subrec to unicast. + */ + + if(name_type == 0x1b) + { + /* We must be a domain master browser in order to + process this packet. */ + + if(!AM_DOMAIN_MASTER_BROWSER(work)) + { + DEBUG(0,("process_get_backup_list_request: domain list requested for workgroup %s \ +and I am not a domain master browser.\n", workgroup_name)); + goto done; + } + + search_subrec = unicast_subnet; + } + else if (name_type == 0x1d) + { + /* We must be a local master browser in order to + process this packet. */ + + if(!AM_LOCAL_MASTER_BROWSER(work)) + { + DEBUG(0,("process_get_backup_list_request: domain list requested for workgroup %s \ +and I am not a local master browser.\n", workgroup_name)); + goto done; + } + } + else + { + DEBUG(0,("process_get_backup_list_request: Invalid name type %x - should be 0x1b or 0x1d.\n", + name_type)); + goto done; + } + + send_backup_list_response(subrec, work, &dgram->source_name, + max_number_requested, token, p->ip, p->port); +done: + END_PROFILE(get_backup_list); +} + +/******************************************************************* + Process a reset browser state packet. + + Diagnostic packet: + 0x1 - Stop being a master browser and become a backup browser. + 0x2 - Discard browse lists, stop being a master browser, try again. + 0x4 - Stop being a master browser forever. + +******************************************************************/ + +void process_reset_browser(struct subnet_record *subrec, + struct packet_struct *p,char *buf) +{ + struct dgram_packet *dgram = &p->packet.dgram; + int state = CVAL(buf,0); + struct subnet_record *sr; + + START_PROFILE(reset_browser); + DEBUG(1,("process_reset_browser: received diagnostic browser reset \ +request from %s IP %s state=0x%X\n", + nmb_namestr(&dgram->source_name), inet_ntoa(p->ip), state)); + + /* Stop being a local master browser on all our broadcast subnets. */ + if (state & 0x1) + { + for (sr = FIRST_SUBNET; sr; sr = NEXT_SUBNET_EXCLUDING_UNICAST(sr)) + { + struct work_record *work; + for (work = sr->workgrouplist; work; work = work->next) + { + if (AM_LOCAL_MASTER_BROWSER(work)) + unbecome_local_master_browser(sr, work, True); + } + } + } + + /* Discard our browse lists. */ + if (state & 0x2) + { + /* + * Calling expire_workgroups_and_servers with a -1 + * time causes all servers not marked with a PERMANENT_TTL + * on the workgroup lists to be discarded, and all + * workgroups with empty server lists to be discarded. + * This means we keep our own server names and workgroup + * as these have a PERMANENT_TTL. + */ + + expire_workgroups_and_servers(-1); + } + + /* Request to stop browsing altogether. */ + if (state & 0x4) + DEBUG(1,("process_reset_browser: ignoring request to stop being a browser.\n")); + + END_PROFILE(reset_browser); +} + +/******************************************************************* + Process an announcement request packet. + We don't respond immediately, we just check it's a request for + our workgroup and then set the flag telling the announce code + in nmbd_sendannounce.c:announce_my_server_names that an + announcement is needed soon. +******************************************************************/ + +void process_announce_request(struct subnet_record *subrec, struct packet_struct *p, char *buf) +{ + struct dgram_packet *dgram = &p->packet.dgram; + struct work_record *work; + char *workgroup_name = dgram->dest_name.name; + + START_PROFILE(announce_request); + DEBUG(3,("process_announce_request: Announce request from %s IP %s to %s.\n", + nmb_namestr(&dgram->source_name), inet_ntoa(p->ip), + nmb_namestr(&dgram->dest_name))); + + /* We only send announcement requests on our workgroup. */ + if(strequal(workgroup_name, lp_workgroup()) == False) + { + DEBUG(7,("process_announce_request: Ignoring announce request for workgroup %s.\n", + workgroup_name)); + goto done; + } + + if((work = find_workgroup_on_subnet(subrec, workgroup_name)) == NULL) + { + DEBUG(0,("process_announce_request: Unable to find workgroup %s on subnet !\n", + workgroup_name)); + goto done; + } + + work->needannounce = True; +done: + END_PROFILE(lm_host_announce); +} + +/******************************************************************* + Process a LanMan announcement request packet. + We don't respond immediately, we just check it's a request for + our workgroup and then set the flag telling that we have found + a LanMan client (DOS or OS/2) and that we will have to start + sending LanMan announcements (unless specifically disabled + through the "lm announce" parameter in smb.conf) +******************************************************************/ + +void process_lm_announce_request(struct subnet_record *subrec, struct packet_struct *p, char *buf) +{ + struct dgram_packet *dgram = &p->packet.dgram; + char *workgroup_name = dgram->dest_name.name; + + START_PROFILE(lm_announce_request); + DEBUG(3,("process_lm_announce_request: Announce request from %s IP %s to %s.\n", + nmb_namestr(&dgram->source_name), inet_ntoa(p->ip), + nmb_namestr(&dgram->dest_name))); + + /* We only send announcement requests on our workgroup. */ + if(strequal(workgroup_name, lp_workgroup()) == False) + { + DEBUG(7,("process_lm_announce_request: Ignoring announce request for workgroup %s.\n", + workgroup_name)); + goto done; + } + + if(find_workgroup_on_subnet(subrec, workgroup_name) == NULL) + { + DEBUG(0,("process_announce_request: Unable to find workgroup %s on subnet !\n", + workgroup_name)); + goto done; + } + + found_lm_clients = True; +done: + END_PROFILE(lm_host_announce); +} diff --git a/source4/nmbd/nmbd_incomingrequests.c b/source4/nmbd/nmbd_incomingrequests.c new file mode 100644 index 0000000000..916f7763f2 --- /dev/null +++ b/source4/nmbd/nmbd_incomingrequests.c @@ -0,0 +1,596 @@ +/* + Unix SMB/CIFS implementation. + NBT netbios routines and daemon - version 2 + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Luke Kenneth Casson Leighton 1994-1998 + Copyright (C) Jeremy Allison 1994-1998 + + 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. + + This file contains all the code to process NetBIOS requests coming + in on port 137. It does not deal with the code needed to service + WINS server requests, but only broadcast and unicast requests. + +*/ + +#include "includes.h" + +/**************************************************************************** +Send a name release response. +**************************************************************************/ + +static void send_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. */ +} + +/**************************************************************************** +Process a name release packet on a broadcast subnet. +Ignore it if it's not one of our names. +****************************************************************************/ + +void process_name_release_request(struct subnet_record *subrec, + struct packet_struct *p) +{ + struct nmb_packet *nmb = &p->packet.nmb; + struct in_addr owner_ip; + 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; + int rcode = 0; + + putip((char *)&owner_ip,&nmb->additional->rdata[2]); + + if(!bcast) + { + /* We should only get broadcast name release packets here. + Anyone trying to release unicast should be going to a WINS + server. If the code gets here, then either we are not a wins + server and they sent it anyway, or we are a WINS server and + the request was malformed. Either way, log an error here. + and send an error reply back. + */ + DEBUG(0,("process_name_release_request: unicast name release request \ +received for name %s from IP %s on subnet %s. Error - should be sent to WINS server\n", + nmb_namestr(question), inet_ntoa(owner_ip), subrec->subnet_name)); + + send_name_release_response(FMT_ERR, p); + return; + } + + DEBUG(3,("process_name_release_request: Name release on name %s, \ +subnet %s from owner IP %s\n", + nmb_namestr(&nmb->question.question_name), + subrec->subnet_name, inet_ntoa(owner_ip))); + + /* If someone is releasing a broadcast group name, just ignore it. */ + if( group && !ismyip(owner_ip) ) + return; + + /* + * Code to work around a bug in FTP OnNet software NBT implementation. + * They do a broadcast name release for WORKGROUP<0> and WORKGROUP<1e> + * names and *don't set the group bit* !!!!! + */ + + if( !group && !ismyip(owner_ip) && strequal(question->name, lp_workgroup()) && + ((question->name_type == 0x0) || (question->name_type == 0x1e))) + { + DEBUG(6,("process_name_release_request: FTP OnNet bug workaround. Ignoring \ +group release name %s from IP %s on subnet %s with no group bit set.\n", + nmb_namestr(question), inet_ntoa(owner_ip), subrec->subnet_name )); + return; + } + + namerec = find_name_on_subnet(subrec, &nmb->question.question_name, FIND_ANY_NAME); + + /* We only care about someone trying to release one of our names. */ + if( namerec + && ( (namerec->data.source == SELF_NAME) + || (namerec->data.source == PERMANENT_NAME) ) ) + { + rcode = ACT_ERR; + DEBUG(0, ("process_name_release_request: Attempt to release name %s from IP %s \ +on subnet %s being rejected as it is one of our names.\n", + nmb_namestr(&nmb->question.question_name), inet_ntoa(owner_ip), subrec->subnet_name)); + } + + if(rcode == 0) + return; + + /* Send a NAME RELEASE RESPONSE (pos/neg) see rfc1002.txt 4.2.10-11 */ + send_name_release_response(rcode, p); +} + +/**************************************************************************** +Send a name registration response. +**************************************************************************/ + +static void send_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. */ + NMB_REG, /* nmbd type code. */ + NMB_NAME_REG_OPCODE, /* opcode. */ + ttl, /* ttl. */ + rdata, /* data to send. */ + 6); /* data length. */ +} + +/**************************************************************************** +Process a name refresh request on a broadcast subnet. +**************************************************************************/ + +void 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; + struct in_addr from_ip; + + putip((char *)&from_ip,&nmb->additional->rdata[2]); + + if(!bcast) + { + /* We should only get broadcast name refresh packets here. + Anyone trying to refresh unicast should be going to a WINS + server. If the code gets here, then either we are not a wins + server and they sent it anyway, or we are a WINS server and + the request was malformed. Either way, log an error here. + and send an error reply back. + */ + DEBUG(0,("process_name_refresh_request: unicast name registration request \ +received for name %s from IP %s on subnet %s.\n", + nmb_namestr(question), inet_ntoa(from_ip), subrec->subnet_name)); + DEBUG(0,("Error - should be sent to WINS server\n")); + + send_name_registration_response(FMT_ERR, 0, p); + return; + } + + /* Just log a message. We really don't care about broadcast name + refreshes. */ + + DEBUG(3,("process_name_refresh_request: Name refresh for name %s \ +IP %s on subnet %s\n", nmb_namestr(question), inet_ntoa(from_ip), subrec->subnet_name)); + +} + +/**************************************************************************** +Process a name registration request on a broadcast subnet. +**************************************************************************/ + +void 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); + BOOL group = (nb_flags & NB_GROUP) ? True : False; + struct name_record *namerec = NULL; + int ttl = nmb->additional->ttl; + struct in_addr from_ip; + + putip((char *)&from_ip,&nmb->additional->rdata[2]); + + if(!bcast) + { + /* We should only get broadcast name registration packets here. + Anyone trying to register unicast should be going to a WINS + server. If the code gets here, then either we are not a wins + server and they sent it anyway, or we are a WINS server and + the request was malformed. Either way, log an error here. + and send an error reply back. + */ + DEBUG(0,("process_name_registration_request: unicast name registration request \ +received for name %s from IP %s on subnet %s. Error - should be sent to WINS server\n", + nmb_namestr(question), inet_ntoa(from_ip), subrec->subnet_name)); + + send_name_registration_response(FMT_ERR, 0, p); + return; + } + + DEBUG(3,("process_name_registration_request: Name registration for name %s \ +IP %s on subnet %s\n", nmb_namestr(question), inet_ntoa(from_ip), subrec->subnet_name)); + + /* See if the name already exists. */ + namerec = find_name_on_subnet(subrec, question, FIND_ANY_NAME); + + /* + * If the name being registered exists and is a WINS_PROXY_NAME + * then delete the WINS proxy name entry so we don't reply erroneously + * later to queries. + */ + + if((namerec != NULL) && (namerec->data.source == WINS_PROXY_NAME)) + { + remove_name_from_namelist( subrec, namerec ); + namerec = NULL; + } + + if (!group) + { + /* Unique name. */ + + if( (namerec != NULL) + && ( (namerec->data.source == SELF_NAME) + || (namerec->data.source == PERMANENT_NAME) + || NAME_GROUP(namerec) ) ) + { + /* No-one can register one of Samba's names, nor can they + register a name that's a group name as a unique name */ + + send_name_registration_response(ACT_ERR, 0, p); + return; + } + else if(namerec != NULL) + { + /* Update the namelist record with the new information. */ + namerec->data.ip[0] = from_ip; + update_name_ttl(namerec, ttl); + + DEBUG(3,("process_name_registration_request: Updated name record %s \ +with IP %s on subnet %s\n",nmb_namestr(&namerec->name),inet_ntoa(from_ip), subrec->subnet_name)); + return; + } + } + else + { + /* Group name. */ + + if( (namerec != NULL) + && !NAME_GROUP(namerec) + && ( (namerec->data.source == SELF_NAME) + || (namerec->data.source == PERMANENT_NAME) ) ) + { + /* Disallow group names when we have a unique name. */ + send_name_registration_response(ACT_ERR, 0, p); + return; + } + } +} + +/**************************************************************************** +This is used to sort names for a name status into a sensible order. +We put our own names first, then in alphabetical order. +**************************************************************************/ + +static int status_compare(char *n1,char *n2) +{ + int l1,l2,l3; + + /* It's a bit tricky because the names are space padded */ + for (l1=0;l1<15 && n1[l1] && n1[l1] != ' ';l1++) ; + for (l2=0;l2<15 && n2[l2] && n2[l2] != ' ';l2++) ; + l3 = strlen(lp_netbios_name()); + + if ((l1==l3) && strncmp(n1,lp_netbios_name(),l3) == 0 && + (l2!=l3 || strncmp(n2,lp_netbios_name(),l3) != 0)) + return -1; + + if ((l2==l3) && strncmp(n2,lp_netbios_name(),l3) == 0 && + (l1!=l3 || strncmp(n1,lp_netbios_name(),l3) != 0)) + return 1; + + return memcmp(n1,n2,18); +} + + +/**************************************************************************** + Process a node status query + ****************************************************************************/ + +void process_node_status_request(struct subnet_record *subrec, struct packet_struct *p) +{ + struct nmb_packet *nmb = &p->packet.nmb; + char *qname = nmb->question.question_name.name; + int ques_type = nmb->question.question_name.name_type; + char rdata[MAX_DGRAM_SIZE]; + char *countptr, *buf, *bufend, *buf0; + int names_added,i; + struct name_record *namerec; + + DEBUG(3,("process_node_status_request: status request for name %s from IP %s on \ +subnet %s.\n", nmb_namestr(&nmb->question.question_name), inet_ntoa(p->ip), + subrec->subnet_name)); + + if((namerec = find_name_on_subnet(subrec, &nmb->question.question_name, + FIND_SELF_NAME)) == 0) + { + DEBUG(1,("process_node_status_request: status request for name %s from IP %s on \ +subnet %s - name not found.\n", nmb_namestr(&nmb->question.question_name), + inet_ntoa(p->ip), subrec->subnet_name)); + + return; + } + + /* this is not an exact calculation. the 46 is for the stats buffer + and the 60 is to leave room for the header etc */ + bufend = &rdata[MAX_DGRAM_SIZE] - (18 + 46 + 60); + countptr = buf = rdata; + buf += 1; + buf0 = buf; + + names_added = 0; + + namerec = (struct name_record *)ubi_trFirst( subrec->namelist ); + + while (buf < bufend) + { + if( (namerec->data.source == SELF_NAME) + || (namerec->data.source == PERMANENT_NAME) ) + { + int name_type = namerec->name.name_type; + + if (!strequal(namerec->name.name,"*") && + !strequal(namerec->name.name,"__SAMBA__") && + (name_type < 0x1b || name_type >= 0x20 || + ques_type < 0x1b || ques_type >= 0x20 || + strequal(qname, namerec->name.name))) + { + /* Start with the name. */ + memset(buf,'\0',18); + slprintf(buf, 17, "%-15.15s",namerec->name.name); + strupper(buf); + + /* Put the name type and netbios flags in the buffer. */ + buf[15] = name_type; + set_nb_flags( &buf[16],namerec->data.nb_flags ); + buf[16] |= NB_ACTIVE; /* all our names are active */ + + buf += 18; + + names_added++; + } + } + + /* Remove duplicate names. */ + if (names_added > 1) { + qsort( buf0, names_added, 18, QSORT_CAST status_compare ); + } + + for( i=1; i < names_added ; i++ ) + { + if (memcmp(buf0 + 18*i,buf0 + 18*(i-1),16) == 0) + { + names_added--; + if (names_added == i) + break; + memmove(buf0 + 18*i,buf0 + 18*(i+1),18*(names_added-i)); + i--; + } + } + + buf = buf0 + 18*names_added; + + namerec = (struct name_record *)ubi_trNext( namerec ); + + if (!namerec) + { + /* End of the subnet specific name list. Now + add the names on the unicast subnet . */ + struct subnet_record *uni_subrec = unicast_subnet; + + if (uni_subrec != subrec) + { + subrec = uni_subrec; + namerec = (struct name_record *)ubi_trFirst( subrec->namelist ); + } + } + if (!namerec) + break; + + } + + SCVAL(countptr,0,names_added); + + /* We don't send any stats as they could be used to attack + the protocol. */ + memset(buf,'\0',46); + + buf += 46; + + /* Send a NODE STATUS RESPONSE */ + reply_netbios_packet(p, /* Packet to reply to. */ + 0, /* Result code. */ + NMB_STATUS, /* nmbd type code. */ + NMB_NAME_QUERY_OPCODE, /* opcode. */ + 0, /* ttl. */ + rdata, /* data to send. */ + PTR_DIFF(buf,rdata)); /* data length. */ +} + + +/*************************************************************************** +Process a name query. + +For broadcast name queries: + + - Only reply if the query is for one of YOUR names. + - NEVER send a negative response to a broadcast query. + +****************************************************************************/ + +void 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; + int name_type = question->name_type; + BOOL bcast = nmb->header.nm_flags.bcast; + int ttl=0; + int rcode = 0; + char *prdata = NULL; + char rdata[6]; + BOOL success = False; + struct name_record *namerec = NULL; + int reply_data_len = 0; + int i; + + DEBUG(3,("process_name_query_request: Name query from %s on subnet %s for name %s\n", + inet_ntoa(p->ip), subrec->subnet_name, nmb_namestr(question))); + + /* Look up the name in the cache - if the request is a broadcast request that + came from a subnet we don't know about then search all the broadcast subnets + for a match (as we don't know what interface the request came in on). */ + + if(subrec == remote_broadcast_subnet) + namerec = find_name_for_remote_broadcast_subnet( question, FIND_ANY_NAME); + else + namerec = find_name_on_subnet(subrec, question, FIND_ANY_NAME); + + /* Check if it is a name that expired */ + if (namerec && + ((namerec->data.death_time != PERMANENT_TTL) && + (namerec->data.death_time < p->timestamp))) { + DEBUG(5,("process_name_query_request: expired name %s\n", nmb_namestr(&namerec->name))); + namerec = NULL; + } + + if (namerec) { + /* + * Always respond to unicast queries. + * Don't respond to broadcast queries unless the query is for + * a name we own, a Primary Domain Controller name, or a WINS_PROXY + * name with type 0 or 0x20. WINS_PROXY names are only ever added + * into the namelist if we were configured as a WINS proxy. + */ + + if (!bcast || + (bcast && ((name_type == 0x1b) || + (namerec->data.source == SELF_NAME) || + (namerec->data.source == PERMANENT_NAME) || + ((namerec->data.source == WINS_PROXY_NAME) && + ((name_type == 0) || (name_type == 0x20)))))) { + /* The requested name is a directed query, or it's SELF or PERMANENT or WINS_PROXY, + or it's a Domain Master type. */ + + /* + * If this is a WINS_PROXY_NAME, then ceck that none of the IP + * addresses we are returning is on the same broadcast subnet + * as the requesting packet. If it is then don't reply as the + * actual machine will be replying also and we don't want two + * replies to a broadcast query. + */ + + if (namerec->data.source == WINS_PROXY_NAME) { + for( i = 0; i < namerec->data.num_ips; i++) { + if (same_net(namerec->data.ip[i], subrec->myip, subrec->mask_ip)) { + DEBUG(5,("process_name_query_request: name %s is a WINS proxy name and is also on the same subnet (%s) as the requestor. Not replying.\n", + nmb_namestr(&namerec->name), subrec->subnet_name )); + return; + } + } + } + + ttl = (namerec->data.death_time != PERMANENT_TTL) ? + namerec->data.death_time - p->timestamp : lp_max_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->data.num_ips == 1) { + prdata = rdata; + } else { + if ((prdata = (char *)malloc( namerec->data.num_ips * 6 )) == NULL) { + DEBUG(0,("process_name_query_request: malloc fail !\n")); + return; + } + } + + for (i = 0; i < namerec->data.num_ips; 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 = namerec->data.num_ips * 6; + success = True; + } + } + + /* + * If a machine is broadcasting a name lookup request and we have lp_wins_proxy() + * set we should initiate a WINS query here. On success we add the resolved name + * into our namelist with a type of WINS_PROXY_NAME and then reply to the query. + */ + + if(!success && (namerec == NULL) && we_are_a_wins_client() && lp_wins_proxy() && + bcast && (subrec != remote_broadcast_subnet)) { + make_wins_proxy_name_query_request( subrec, p, question ); + return; + } + + if (!success && bcast) { + if(prdata != rdata) + SAFE_FREE(prdata); + return; /* Never reply with a negative response to broadcasts. */ + } + + /* + * Final check. From observation, if a unicast packet is sent + * to a non-WINS server with the recursion desired bit set + * then never send a negative response. + */ + + if(!success && !bcast && nmb->header.nm_flags.recursion_desired) { + if(prdata != rdata) + SAFE_FREE(prdata); + return; + } + + if (success) { + rcode = 0; + DEBUG(3,("OK\n")); + } else { + rcode = NAM_ERR; + DEBUG(3,("UNKNOWN\n")); + } + + /* See rfc1002.txt 4.2.13. */ + + reply_netbios_packet(p, /* Packet to reply to. */ + rcode, /* Result code. */ + NMB_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); +} diff --git a/source4/nmbd/nmbd_lmhosts.c b/source4/nmbd/nmbd_lmhosts.c new file mode 100644 index 0000000000..c47384c819 --- /dev/null +++ b/source4/nmbd/nmbd_lmhosts.c @@ -0,0 +1,104 @@ +/* + Unix SMB/CIFS implementation. + NBT netbios routines and daemon - version 2 + Copyright (C) Jeremy Allison 1994-1998 + + 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. + + Revision History: + + Handle lmhosts file reading. + +*/ + +#include "includes.h" + +/**************************************************************************** +Load a lmhosts file. +****************************************************************************/ +void load_lmhosts_file(char *fname) +{ + char *name; + int name_type; + struct in_addr ipaddr; + XFILE *fp = startlmhosts( fname ); + TALLOC_CTX *mem_ctx; + + if (!fp) { + DEBUG(2,("load_lmhosts_file: Can't open lmhosts file %s. Error was %s\n", + fname, strerror(errno))); + return; + } + mem_ctx = talloc_init("load_lmhosts_files"); + if (!mem_ctx) { + DEBUG(2,("load_lmhosts_file: No memory to open lmhosts file %s. Error was %s\n", + fname, strerror(errno))); + return; + } + while (getlmhostsent(mem_ctx, fp, name, &name_type, &ipaddr) ) + { + struct subnet_record *subrec = NULL; + enum name_source source = LMHOSTS_NAME; + + /* We find a relevent subnet to put this entry on, then add it. */ + /* Go through all the broadcast subnets and see if the mask matches. */ + for (subrec = FIRST_SUBNET; subrec ; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) + { + if(same_net(ipaddr, subrec->bcast_ip, subrec->mask_ip)) + break; + } + + /* If none match add the name to the remote_broadcast_subnet. */ + if(subrec == NULL) + subrec = remote_broadcast_subnet; + + if(name_type == -1) + { + /* Add the (0) and (0x20) names directly into the namelist for this subnet. */ + (void)add_name_to_subnet(subrec,name,0x00,(uint16)NB_ACTIVE,PERMANENT_TTL,source,1,&ipaddr); + (void)add_name_to_subnet(subrec,name,0x20,(uint16)NB_ACTIVE,PERMANENT_TTL,source,1,&ipaddr); + } + else + { + /* Add the given name type to the subnet namelist. */ + (void)add_name_to_subnet(subrec,name,name_type,(uint16)NB_ACTIVE,PERMANENT_TTL,source,1,&ipaddr); + } + } + + endlmhosts(fp); +} + +/**************************************************************************** + Find a name read from the lmhosts file. We secretly check the names on + the remote_broadcast_subnet as if the name was added to a regular broadcast + subnet it will be found by normal name query processing. +****************************************************************************/ + +BOOL find_name_in_lmhosts(struct nmb_name *nmbname, struct name_record **namerecp) +{ + struct name_record *namerec; + + *namerecp = NULL; + + if((namerec = find_name_on_subnet(remote_broadcast_subnet, nmbname, + FIND_ANY_NAME))==NULL) + return False; + + if(!NAME_IS_ACTIVE(namerec) || (namerec->data.source != LMHOSTS_NAME)) + return False; + + *namerecp = namerec; + return True; +} diff --git a/source4/nmbd/nmbd_logonnames.c b/source4/nmbd/nmbd_logonnames.c new file mode 100644 index 0000000000..40edc68800 --- /dev/null +++ b/source4/nmbd/nmbd_logonnames.c @@ -0,0 +1,172 @@ +/* + Unix SMB/CIFS implementation. + NBT netbios routines and daemon - version 2 + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Luke Kenneth Casson Leighton 1994-1998 + Copyright (C) Jeremy Allison 1994-1998 + + 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" + +extern struct in_addr allones_ip; + +extern uint16 samba_nb_type; /* Samba's NetBIOS type. */ + +/**************************************************************************** + Fail to become a Logon server on a subnet. + ****************************************************************************/ +static void become_logon_server_fail(struct subnet_record *subrec, + struct response_record *rrec, + struct nmb_name *fail_name) +{ + struct work_record *work = find_workgroup_on_subnet(subrec, fail_name->name); + struct server_record *servrec; + + if(!work) + { + DEBUG(0,("become_logon_server_fail: Error - cannot find \ +workgroup %s on subnet %s\n", fail_name->name, subrec->subnet_name)); + return; + } + + if((servrec = find_server_in_workgroup( work, lp_netbios_name())) == NULL) + { + DEBUG(0,("become_logon_server_fail: Error - cannot find server %s \ +in workgroup %s on subnet %s\n", + lp_netbios_name(), fail_name->name, subrec->subnet_name)); + work->log_state = LOGON_NONE; + return; + } + + /* Set the state back to LOGON_NONE. */ + work->log_state = LOGON_NONE; + + servrec->serv.type &= ~SV_TYPE_DOMAIN_CTRL; + + DEBUG(0,("become_logon_server_fail: Failed to become a domain master for \ +workgroup %s on subnet %s. Couldn't register name %s.\n", + work->work_group, subrec->subnet_name, nmb_namestr(fail_name))); + +} + +/**************************************************************************** + Become a Logon server on a subnet. + ****************************************************************************/ + +static void become_logon_server_success(struct subnet_record *subrec, + struct userdata_struct *userdata, + struct nmb_name *registered_name, + uint16 nb_flags, + int ttl, struct in_addr registered_ip) +{ + struct work_record *work = find_workgroup_on_subnet( subrec, registered_name->name); + struct server_record *servrec; + + if(!work) + { + DEBUG(0,("become_logon_server_success: Error - cannot find \ +workgroup %s on subnet %s\n", registered_name->name, subrec->subnet_name)); + return; + } + + if((servrec = find_server_in_workgroup( work, lp_netbios_name())) == NULL) + { + DEBUG(0,("become_logon_server_success: Error - cannot find server %s \ +in workgroup %s on subnet %s\n", + lp_netbios_name(), registered_name->name, subrec->subnet_name)); + work->log_state = LOGON_NONE; + return; + } + + /* Set the state in the workgroup structure. */ + work->log_state = LOGON_SRV; /* Become domain master. */ + + /* Update our server status. */ + servrec->serv.type |= (SV_TYPE_NT|SV_TYPE_DOMAIN_MEMBER); + /* To allow Win95 policies to load we need to set type domain + controller. + */ + servrec->serv.type |= SV_TYPE_DOMAIN_CTRL; + + /* Tell the namelist writer to write out a change. */ + subrec->work_changed = True; + + /* + * Add the WORKGROUP<1C> name to the UNICAST subnet with the IP address + * for this subnet so we will respond to queries on this name. + */ + { + struct nmb_name nmbname; + make_nmb_name(&nmbname,lp_workgroup(),0x1c); + insert_permanent_name_into_unicast(subrec, &nmbname, 0x1c); + } + + DEBUG(0,("become_logon_server_success: Samba is now a logon server \ +for workgroup %s on subnet %s\n", work->work_group, subrec->subnet_name)); +} + +/******************************************************************* + Become a logon server by attempting to register the WORKGROUP<1c> + group name. +******************************************************************/ + +static void become_logon_server(struct subnet_record *subrec, + struct work_record *work) +{ + DEBUG(2,("become_logon_server: Atempting to become logon server for workgroup %s \ +on subnet %s\n", work->work_group,subrec->subnet_name)); + + DEBUG(3,("become_logon_server: go to first stage: register %s<1c> name\n", + work->work_group)); + work->log_state = LOGON_WAIT; + + register_name(subrec, work->work_group,0x1c,samba_nb_type|NB_GROUP, + become_logon_server_success, + become_logon_server_fail, NULL); +} + +/***************************************************************************** + Add the internet group <1c> logon names by unicast and broadcast. + ****************************************************************************/ +void add_logon_names(void) +{ + struct subnet_record *subrec; + + for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_INCLUDING_UNICAST(subrec)) + { + struct work_record *work = find_workgroup_on_subnet(subrec, lp_workgroup()); + + if (work && (work->log_state == LOGON_NONE)) + { + struct nmb_name nmbname; + make_nmb_name(&nmbname,lp_workgroup(),0x1c); + + if (find_name_on_subnet(subrec, &nmbname, FIND_SELF_NAME) == NULL) + { + if( DEBUGLVL( 0 ) ) + { + dbgtext( "add_domain_logon_names:\n" ); + dbgtext( "Attempting to become logon server " ); + dbgtext( "for workgroup %s ", lp_workgroup() ); + dbgtext( "on subnet %s\n", subrec->subnet_name ); + } + become_logon_server(subrec, work); + } + } + } +} diff --git a/source4/nmbd/nmbd_mynames.c b/source4/nmbd/nmbd_mynames.c new file mode 100644 index 0000000000..dd66821839 --- /dev/null +++ b/source4/nmbd/nmbd_mynames.c @@ -0,0 +1,228 @@ +/* + Unix SMB/CIFS implementation. + NBT netbios routines and daemon - version 2 + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Luke Kenneth Casson Leighton 1994-1998 + Copyright (C) Jeremy Allison 1994-1998 + + 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" + +extern uint16 samba_nb_type; /* Samba's NetBIOS type. */ + +/**************************************************************************** + Fail funtion when registering my netbios names. + **************************************************************************/ + +static void my_name_register_failed(struct subnet_record *subrec, + struct response_record *rrec, struct nmb_name *nmbname) +{ + DEBUG(0,("my_name_register_failed: Failed to register my name %s on subnet %s.\n", + nmb_namestr(nmbname), subrec->subnet_name)); +} + + +/**************************************************************************** + Add my workgroup and my given names to one subnet + Also add the magic Samba names. + **************************************************************************/ +void register_my_workgroup_one_subnet(struct subnet_record *subrec) +{ + int i; + + struct work_record *work; + + /* Create the workgroup on the subnet. */ + if((work = create_workgroup_on_subnet(subrec, lp_workgroup(), + PERMANENT_TTL)) == NULL) { + DEBUG(0,("register_my_workgroup_and_names: Failed to create my workgroup %s on subnet %s. \ +Exiting.\n", lp_workgroup(), subrec->subnet_name)); + return; + } + + /* Each subnet entry, except for the wins_server_subnet has + the magic Samba names. */ + add_samba_names_to_subnet(subrec); + + /* Register all our names including aliases. */ + for (i=0; my_netbios_names(i); i++) { + register_name(subrec, my_netbios_names(i),0x20,samba_nb_type, + NULL, + my_name_register_failed, NULL); + register_name(subrec, my_netbios_names(i),0x03,samba_nb_type, + NULL, + my_name_register_failed, NULL); + register_name(subrec, my_netbios_names(i),0x00,samba_nb_type, + NULL, + my_name_register_failed, NULL); + } + + /* Initiate election processing, register the workgroup names etc. */ + initiate_myworkgroup_startup(subrec, work); +} + +/******************************************************************* + Utility function to add a name to the unicast subnet, or add in + our IP address if it already exists. +******************************************************************/ + +static void insert_refresh_name_into_unicast( struct subnet_record *subrec, + struct nmb_name *nmbname, uint16 nb_type ) +{ + struct name_record *namerec; + + if (!we_are_a_wins_client()) { + insert_permanent_name_into_unicast(subrec, nmbname, nb_type); + return; + } + + if((namerec = find_name_on_subnet(unicast_subnet, nmbname, FIND_SELF_NAME)) == NULL) + { + /* The name needs to be created on the unicast subnet. */ + (void)add_name_to_subnet( unicast_subnet, nmbname->name, + nmbname->name_type, nb_type, + MIN(lp_max_ttl(), MAX_REFRESH_TIME), SELF_NAME, 1, &subrec->myip); + } + else + { + /* The name already exists on the unicast subnet. Add our local + IP for the given broadcast subnet to the name. */ + add_ip_to_name_record( namerec, subrec->myip); + } +} + +/**************************************************************************** + Add my workgroup and my given names to the subnet lists. + Also add the magic Samba names. + **************************************************************************/ + +BOOL register_my_workgroup_and_names(void) +{ + struct subnet_record *subrec; + int i; + + for(subrec = FIRST_SUBNET; + subrec; + subrec = NEXT_SUBNET_INCLUDING_UNICAST(subrec)) + { + register_my_workgroup_one_subnet(subrec); + } + + /* We still need to add the magic Samba + names and the netbios names to the unicast subnet directly. This is + to allow unicast node status requests and queries to still work + in a broadcast only environment. */ + + add_samba_names_to_subnet(unicast_subnet); + + for (i=0; my_netbios_names(i); i++) + { + for(subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) + { + /* + * Ensure all the IP addresses are added if we are multihomed. + */ + struct nmb_name nmbname; + + make_nmb_name(&nmbname, my_netbios_names(i),0x20); + insert_refresh_name_into_unicast(subrec, &nmbname, samba_nb_type); + + make_nmb_name(&nmbname, my_netbios_names(i),0x3); + insert_refresh_name_into_unicast(subrec, &nmbname, samba_nb_type); + + make_nmb_name(&nmbname, my_netbios_names(i),0x0); + insert_refresh_name_into_unicast(subrec, &nmbname, samba_nb_type); + } + } + + /* + * Add the WORKGROUP<0> and WORKGROUP<1e> group names to the unicast subnet + * also for the same reasons. + */ + + for(subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) + { + /* + * Ensure all the IP addresses are added if we are multihomed. + */ + struct nmb_name nmbname; + + make_nmb_name(&nmbname, lp_workgroup(), 0x0); + insert_refresh_name_into_unicast(subrec, &nmbname, samba_nb_type|NB_GROUP); + + make_nmb_name(&nmbname, lp_workgroup(), 0x1e); + insert_refresh_name_into_unicast(subrec, &nmbname, samba_nb_type|NB_GROUP); + } + + /* + * We need to add the Samba names to the remote broadcast subnet, + * as NT 4.x does directed broadcast requests to the *<0x0> name. + */ + add_samba_names_to_subnet(remote_broadcast_subnet); + + return True; +} + +/**************************************************************************** + Remove all the names we registered. +**************************************************************************/ +void release_wins_names(void) +{ + struct subnet_record *subrec = unicast_subnet; + struct name_record *namerec, *nextnamerec; + + for (namerec = (struct name_record *)ubi_trFirst( subrec->namelist ); + namerec; + namerec = nextnamerec) { + nextnamerec = (struct name_record *)ubi_trNext( namerec ); + if( (namerec->data.source == SELF_NAME) + && !NAME_IS_DEREGISTERING(namerec) ) + release_name( subrec, namerec, standard_success_release, + NULL, NULL); + } +} + +/******************************************************************* + Refresh our registered names with WINS + ******************************************************************/ +void refresh_my_names(time_t t) +{ + struct name_record *namerec; + + if (wins_srv_count() < 1) return; + + for (namerec = (struct name_record *)ubi_trFirst(unicast_subnet->namelist); + namerec; + namerec = (struct name_record *)ubi_trNext(namerec)) { + /* Each SELF name has an individual time to be refreshed. */ + if ((namerec->data.source == SELF_NAME) && + (namerec->data.refresh_time < t) && + (namerec->data.death_time != PERMANENT_TTL)) { + /* We cheat here and pretend the refresh is going to be + successful & update the refresh times. This stops + multiple refresh calls being done. We actually + deal with refresh failure in the fail_fn. + */ + if (!is_refresh_already_queued(unicast_subnet, namerec)) { + wins_refresh_name(namerec); + } + namerec->data.death_time = t + lp_max_ttl(); + namerec->data.refresh_time = t + MIN(lp_max_ttl()/2, MAX_REFRESH_TIME); + } + } +} diff --git a/source4/nmbd/nmbd_namelistdb.c b/source4/nmbd/nmbd_namelistdb.c new file mode 100644 index 0000000000..932d926a91 --- /dev/null +++ b/source4/nmbd/nmbd_namelistdb.c @@ -0,0 +1,624 @@ +/* + Unix SMB/CIFS implementation. + NBT netbios routines and daemon - version 2 + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Luke Kenneth Casson Leighton 1994-1998 + Copyright (C) Jeremy Allison 1994-1998 + + 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" + +uint16 samba_nb_type = 0; /* samba's NetBIOS name type */ + + +/* ************************************************************************** ** + * Set Samba's NetBIOS name type. + * ************************************************************************** ** + */ +void set_samba_nb_type(void) + { + if( lp_wins_support() || wins_srv_count() ) + samba_nb_type = NB_HFLAG; /* samba is a 'hybrid' node type. */ + else + samba_nb_type = NB_BFLAG; /* samba is broadcast-only node type. */ + } /* set_samba_nb_type */ + +/* ************************************************************************** ** + * Convert a NetBIOS name to upper case. + * ************************************************************************** ** + */ +static void upcase_name( struct nmb_name *target, struct nmb_name *source ) + { + int i; + + if( NULL != source ) + (void)memcpy( target, source, sizeof( struct nmb_name ) ); + + strupper( target->name ); + strupper( target->scope ); + + /* fudge... 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( target->name ); i < sizeof( target->name ); i++ ) + target->name[i] = '\0'; + for( i = strlen( target->scope ); i < sizeof( target->scope ); i++ ) + target->scope[i] = '\0'; + } /* upcase_name */ + +/* ************************************************************************** ** + * Add a new or overwrite an existing namelist entry. + * ************************************************************************** ** + */ +static void update_name_in_namelist( struct subnet_record *subrec, + struct name_record *namerec ) + { + struct name_record *oldrec = NULL; + + (void)ubi_trInsert( subrec->namelist, namerec, &(namerec->name), &oldrec ); + if( oldrec ) + { + SAFE_FREE( oldrec->data.ip ); + SAFE_FREE( oldrec ); + } + } /* update_name_in_namelist */ + +/* ************************************************************************** ** + * Remove a name from the namelist. + * ************************************************************************** ** + */ +void remove_name_from_namelist( struct subnet_record *subrec, + struct name_record *namerec ) + { + (void)ubi_trRemove( subrec->namelist, namerec ); + + SAFE_FREE(namerec->data.ip); + + ZERO_STRUCTP(namerec); + SAFE_FREE(namerec); + + subrec->namelist_changed = True; + } /* remove_name_from_namelist */ + +/* ************************************************************************** ** + * Find a name in a subnet. + * ************************************************************************** ** + */ +struct name_record *find_name_on_subnet( struct subnet_record *subrec, + struct nmb_name *nmbname, + BOOL self_only ) + { + struct nmb_name uc_name[1]; + struct name_record *name_ret; + + upcase_name( uc_name, nmbname ); + name_ret = (struct name_record *)ubi_trFind( subrec->namelist, uc_name ); + if( name_ret ) + { + /* Self names only - these include permanent names. */ + if( self_only + && (name_ret->data.source != SELF_NAME) + && (name_ret->data.source != PERMANENT_NAME) ) + { + DEBUG( 9, + ( "find_name_on_subnet: on subnet %s - self name %s NOT FOUND\n", + subrec->subnet_name, nmb_namestr(nmbname) ) ); + return( NULL ); + } + DEBUG( 9, ("find_name_on_subnet: on subnet %s - found name %s source=%d\n", + subrec->subnet_name, nmb_namestr(nmbname), name_ret->data.source) ); + return( name_ret ); + } + DEBUG( 9, + ( "find_name_on_subnet: on subnet %s - name %s NOT FOUND\n", + subrec->subnet_name, nmb_namestr(nmbname) ) ); + return( NULL ); + } /* find_name_on_subnet */ + +/* ************************************************************************** ** + * Find a name over all known broadcast subnets. + * ************************************************************************** ** + */ +struct name_record *find_name_for_remote_broadcast_subnet( + struct nmb_name *nmbname, + BOOL self_only ) + { + struct subnet_record *subrec; + struct name_record *namerec = NULL; + + for( subrec = FIRST_SUBNET; + subrec; + subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec) ) + { + if( NULL != (namerec = find_name_on_subnet(subrec, nmbname, self_only)) ) + break; + } + + return( namerec ); + } /* find_name_for_remote_broadcast_subnet */ + +/* ************************************************************************** ** + * Update the ttl of an entry in a subnet name list. + * ************************************************************************** ** + */ +void update_name_ttl( struct name_record *namerec, int ttl ) +{ + time_t time_now = time(NULL); + + if( namerec->data.death_time != PERMANENT_TTL ) + namerec->data.death_time = time_now + ttl; + + namerec->data.refresh_time = time_now + MIN((ttl/2), MAX_REFRESH_TIME); + + namerec->subnet->namelist_changed = True; +} /* update_name_ttl */ + +/* ************************************************************************** ** + * Add an entry to a subnet name list. + * ************************************************************************** ** + */ +struct name_record *add_name_to_subnet( struct subnet_record *subrec, + const char *name, + int type, + uint16 nb_flags, + int ttl, + enum name_source source, + int num_ips, + struct in_addr *iplist) +{ + struct name_record *namerec; + time_t time_now = time(NULL); + + namerec = (struct name_record *)malloc( sizeof(*namerec) ); + if( NULL == namerec ) + { + DEBUG( 0, ( "add_name_to_subnet: malloc fail.\n" ) ); + return( NULL ); + } + + memset( (char *)namerec, '\0', sizeof(*namerec) ); + namerec->data.ip = (struct in_addr *)malloc( sizeof(struct in_addr) + * num_ips ); + if( NULL == namerec->data.ip ) + { + DEBUG( 0, ( "add_name_to_subnet: malloc fail when creating ip_flgs.\n" ) ); + + ZERO_STRUCTP(namerec); + SAFE_FREE(namerec); + return NULL; + } + + namerec->subnet = subrec; + + make_nmb_name(&namerec->name, name, type); + upcase_name(&namerec->name, NULL ); + + /* Enter the name as active. */ + namerec->data.nb_flags = nb_flags | NB_ACTIVE; + namerec->data.wins_flags = WINS_ACTIVE; + + /* If it's our primary name, flag it as so. */ + if( strequal( my_netbios_names(0), name ) ) + namerec->data.nb_flags |= NB_PERM; + + /* Copy the IPs. */ + namerec->data.num_ips = num_ips; + memcpy( (namerec->data.ip), iplist, num_ips * sizeof(struct in_addr) ); + + /* Data source. */ + namerec->data.source = source; + + /* Setup the death_time and refresh_time. */ + if( ttl == PERMANENT_TTL ) + namerec->data.death_time = PERMANENT_TTL; + else + namerec->data.death_time = time_now + ttl; + + namerec->data.refresh_time = time_now + MIN((ttl/2), MAX_REFRESH_TIME); + + /* Now add the record to the name list. */ + update_name_in_namelist( subrec, namerec ); + + DEBUG( 3, ( "add_name_to_subnet: Added netbios name %s with first IP %s \ +ttl=%d nb_flags=%2x to subnet %s\n", + nmb_namestr( &namerec->name ), + inet_ntoa( *iplist ), + ttl, + (unsigned int)nb_flags, + subrec->subnet_name ) ); + + subrec->namelist_changed = True; + + return(namerec); +} + +/******************************************************************* + Utility function automatically called when a name refresh or register + succeeds. By definition this is a SELF_NAME (or we wouldn't be registering + it). + ******************************************************************/ + +void standard_success_register(struct subnet_record *subrec, + struct userdata_struct *userdata, + struct nmb_name *nmbname, uint16 nb_flags, int ttl, + struct in_addr registered_ip) +{ + struct name_record *namerec; + + namerec = find_name_on_subnet( subrec, nmbname, FIND_SELF_NAME ); + if( NULL == namerec ) + (void)add_name_to_subnet( subrec, nmbname->name, nmbname->name_type, + nb_flags, ttl, SELF_NAME, 1, ®istered_ip ); + else + update_name_ttl( namerec, ttl ); +} + +/******************************************************************* + Utility function automatically called when a name refresh or register + fails. Note that this is only ever called on a broadcast subnet with + one IP address per name. This is why it can just delete the name + without enumerating the IP adresses. JRA. + ******************************************************************/ + +void standard_fail_register( struct subnet_record *subrec, + struct response_record *rrec, + struct nmb_name *nmbname ) +{ + struct name_record *namerec; + + namerec = find_name_on_subnet( subrec, nmbname, FIND_SELF_NAME ); + + DEBUG( 0, ( "standard_fail_register: Failed to register/refresh name %s \ +on subnet %s\n", + nmb_namestr(nmbname), subrec->subnet_name) ); + + /* Remove the name from the subnet. */ + if( namerec ) + remove_name_from_namelist(subrec, namerec); +} + +/******************************************************************* + Utility function to remove an IP address from a name record. + ******************************************************************/ + +static void remove_nth_ip_in_record( struct name_record *namerec, int ind) +{ + if( ind != namerec->data.num_ips ) + memmove( (char *)(&namerec->data.ip[ind]), + (char *)(&namerec->data.ip[ind+1]), + ( namerec->data.num_ips - ind - 1) * sizeof(struct in_addr) ); + + namerec->data.num_ips--; + namerec->subnet->namelist_changed = True; +} + +/******************************************************************* + Utility function to check if an IP address exists in a name record. + ******************************************************************/ + +BOOL find_ip_in_name_record( struct name_record *namerec, struct in_addr ip ) +{ + int i; + + for(i = 0; i < namerec->data.num_ips; i++) + if(ip_equal( namerec->data.ip[i], ip)) + return True; + + return False; +} + +/******************************************************************* + Utility function to add an IP address to a name record. + ******************************************************************/ + +void add_ip_to_name_record( struct name_record *namerec, struct in_addr new_ip ) +{ + struct in_addr *new_list; + + /* Don't add one we already have. */ + if( find_ip_in_name_record( namerec, new_ip ) ) + return; + + new_list = (struct in_addr *)malloc( (namerec->data.num_ips + 1) + * sizeof(struct in_addr) ); + if( NULL == new_list ) + { + DEBUG(0,("add_ip_to_name_record: Malloc fail !\n")); + return; + } + + memcpy( (char *)new_list, + (char *)namerec->data.ip, + namerec->data.num_ips * sizeof(struct in_addr) ); + new_list[namerec->data.num_ips] = new_ip; + + SAFE_FREE(namerec->data.ip); + namerec->data.ip = new_list; + namerec->data.num_ips += 1; + + namerec->subnet->namelist_changed = True; +} + +/******************************************************************* + Utility function to remove an IP address from a name record. + ******************************************************************/ + +void remove_ip_from_name_record( struct name_record *namerec, + struct in_addr remove_ip ) +{ + /* Try and find the requested ip address - remove it. */ + int i; + int orig_num = namerec->data.num_ips; + + for(i = 0; i < orig_num; i++) + if( ip_equal( remove_ip, namerec->data.ip[i]) ) + { + remove_nth_ip_in_record( namerec, i); + break; + } +} + +/******************************************************************* + Utility function that release_name callers can plug into as the + success function when a name release is successful. Used to save + duplication of success_function code. + ******************************************************************/ + +void standard_success_release( struct subnet_record *subrec, + struct userdata_struct *userdata, + struct nmb_name *nmbname, + struct in_addr released_ip ) +{ + struct name_record *namerec; + + namerec = find_name_on_subnet( subrec, nmbname, FIND_ANY_NAME ); + + if( namerec == NULL ) + { + DEBUG( 0, ( "standard_success_release: Name release for name %s IP %s \ +on subnet %s. Name was not found on subnet.\n", + nmb_namestr(nmbname), + inet_ntoa(released_ip), + subrec->subnet_name) ); + return; + } + else + { + int orig_num = namerec->data.num_ips; + + remove_ip_from_name_record( namerec, released_ip ); + + if( namerec->data.num_ips == orig_num ) + DEBUG( 0, ( "standard_success_release: Name release for name %s IP %s \ +on subnet %s. This ip is not known for this name.\n", + nmb_namestr(nmbname), + inet_ntoa(released_ip), + subrec->subnet_name ) ); + } + + if( namerec->data.num_ips == 0 ) + remove_name_from_namelist( subrec, namerec ); +} + +/******************************************************************* + Expires old names in a subnet namelist. + ******************************************************************/ + +void expire_names_on_subnet(struct subnet_record *subrec, time_t t) +{ + struct name_record *namerec; + struct name_record *next_namerec; + + for( namerec = (struct name_record *)ubi_trFirst( subrec->namelist ); + namerec; + namerec = next_namerec ) + { + next_namerec = (struct name_record *)ubi_trNext( namerec ); + if( (namerec->data.death_time != PERMANENT_TTL) + && (namerec->data.death_time < t) ) + { + if( namerec->data.source == SELF_NAME ) + { + DEBUG( 3, ( "expire_names_on_subnet: Subnet %s not expiring SELF \ +name %s\n", + subrec->subnet_name, nmb_namestr(&namerec->name) ) ); + namerec->data.death_time += 300; + namerec->subnet->namelist_changed = True; + continue; + } + DEBUG(3,("expire_names_on_subnet: Subnet %s - removing expired name %s\n", + subrec->subnet_name, nmb_namestr(&namerec->name))); + + remove_name_from_namelist( subrec, namerec ); + } + } +} + +/******************************************************************* + Expires old names in all subnet namelists. + ******************************************************************/ + +void expire_names(time_t t) +{ + struct subnet_record *subrec; + + for( subrec = FIRST_SUBNET; + subrec; + subrec = NEXT_SUBNET_INCLUDING_UNICAST(subrec) ) + { + expire_names_on_subnet( subrec, t ); + } +} + +/**************************************************************************** + Add the magic samba names, useful for finding samba servers. + These go directly into the name list for a particular subnet, + without going through the normal registration process. + When adding them to the unicast subnet, add them as a list of + all broadcast subnet IP addresses. +**************************************************************************/ + +void add_samba_names_to_subnet( struct subnet_record *subrec ) +{ + struct in_addr *iplist = &subrec->myip; + int num_ips = 1; + + /* These names are added permanently (ttl of zero) and will NOT be + refreshed. */ + + if( (subrec == unicast_subnet) + || (subrec == wins_server_subnet) + || (subrec == remote_broadcast_subnet) ) + { + struct subnet_record *bcast_subrecs; + int i; + /* Create an IP list containing all our known subnets. */ + + num_ips = iface_count(); + iplist = (struct in_addr *)malloc( num_ips * sizeof(struct in_addr) ); + if( NULL == iplist ) + { + DEBUG(0,("add_samba_names_to_subnet: Malloc fail !\n")); + return; + } + + for( bcast_subrecs = FIRST_SUBNET, i = 0; + bcast_subrecs; + bcast_subrecs = NEXT_SUBNET_EXCLUDING_UNICAST(bcast_subrecs), i++ ) + iplist[i] = bcast_subrecs->myip; + + } + + (void)add_name_to_subnet(subrec,"*",0x0,samba_nb_type, PERMANENT_TTL, + PERMANENT_NAME, num_ips, iplist); + (void)add_name_to_subnet(subrec,"*",0x20,samba_nb_type,PERMANENT_TTL, + PERMANENT_NAME, num_ips, iplist); + (void)add_name_to_subnet(subrec,"__SAMBA__",0x20,samba_nb_type,PERMANENT_TTL, + PERMANENT_NAME, num_ips, iplist); + (void)add_name_to_subnet(subrec,"__SAMBA__",0x00,samba_nb_type,PERMANENT_TTL, + PERMANENT_NAME, num_ips, iplist); + + if(iplist != &subrec->myip) + SAFE_FREE(iplist); +} + +/**************************************************************************** + Dump the contents of the namelists on all the subnets (including unicast) + into a file. Initiated by SIGHUP - used to debug the state of the namelists. +**************************************************************************/ + +static void dump_subnet_namelist( struct subnet_record *subrec, XFILE *fp) +{ + struct name_record *namerec; + const char *src_type; + struct tm *tm; + int i; + + x_fprintf(fp, "Subnet %s\n----------------------\n", subrec->subnet_name); + for( namerec = (struct name_record *)ubi_trFirst( subrec->namelist ); + namerec; + namerec = (struct name_record *)ubi_trNext( namerec ) ) + { + x_fprintf(fp,"\tName = %s\t", nmb_namestr(&namerec->name)); + switch(namerec->data.source) + { + case LMHOSTS_NAME: + src_type = "LMHOSTS_NAME"; + break; + case WINS_PROXY_NAME: + src_type = "WINS_PROXY_NAME"; + break; + case REGISTER_NAME: + src_type = "REGISTER_NAME"; + break; + case SELF_NAME: + src_type = "SELF_NAME"; + break; + case DNS_NAME: + src_type = "DNS_NAME"; + break; + case DNSFAIL_NAME: + src_type = "DNSFAIL_NAME"; + break; + case PERMANENT_NAME: + src_type = "PERMANENT_NAME"; + break; + default: + src_type = "unknown!"; + break; + } + x_fprintf(fp,"Source = %s\nb_flags = %x\t", src_type, namerec->data.nb_flags); + + if(namerec->data.death_time != PERMANENT_TTL) + { + tm = LocalTime(&namerec->data.death_time); + x_fprintf(fp, "death_time = %s\t", asctime(tm)); + } + else + x_fprintf(fp, "death_time = PERMANENT\t"); + + if(namerec->data.refresh_time != PERMANENT_TTL) + { + tm = LocalTime(&namerec->data.refresh_time); + x_fprintf(fp, "refresh_time = %s\n", asctime(tm)); + } + else + x_fprintf(fp, "refresh_time = PERMANENT\n"); + + x_fprintf(fp, "\t\tnumber of IPS = %d", namerec->data.num_ips); + for(i = 0; i < namerec->data.num_ips; i++) + x_fprintf(fp, "\t%s", inet_ntoa(namerec->data.ip[i])); + + x_fprintf(fp, "\n\n"); + } +} + +/**************************************************************************** + Dump the contents of the namelists on all the subnets (including unicast) + into a file. Initiated by SIGHUP - used to debug the state of the namelists. +**************************************************************************/ + +void dump_all_namelists(void) +{ + XFILE *fp; + struct subnet_record *subrec; + + fp = x_fopen(lock_path("namelist.debug"),O_WRONLY|O_CREAT|O_TRUNC, 0644); + + if (!fp) + { + DEBUG(0,("dump_all_namelists: Can't open file %s. Error was %s\n", + "namelist.debug",strerror(errno))); + return; + } + + for( subrec = FIRST_SUBNET; + subrec; + subrec = NEXT_SUBNET_INCLUDING_UNICAST(subrec) ) + dump_subnet_namelist( subrec, fp ); + + if( !we_are_a_wins_client() ) + dump_subnet_namelist( unicast_subnet, fp ); + + if( remote_broadcast_subnet->namelist != NULL ) + dump_subnet_namelist( remote_broadcast_subnet, fp ); + + if( wins_server_subnet != NULL ) + dump_subnet_namelist( wins_server_subnet, fp ); + x_fclose( fp ); +} diff --git a/source4/nmbd/nmbd_namequery.c b/source4/nmbd/nmbd_namequery.c new file mode 100644 index 0000000000..8995e9ac52 --- /dev/null +++ b/source4/nmbd/nmbd_namequery.c @@ -0,0 +1,304 @@ +/* + Unix SMB/CIFS implementation. + NBT netbios routines and daemon - version 2 + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Luke Kenneth Casson Leighton 1994-1998 + Copyright (C) Jeremy Allison 1994-1998 + + 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" + +/**************************************************************************** + Deal with a response packet when querying a name. +****************************************************************************/ + +static void query_name_response( struct subnet_record *subrec, + struct response_record *rrec, + struct packet_struct *p) +{ + struct nmb_packet *nmb = &p->packet.nmb; + BOOL success = False; + struct nmb_name *question_name = + &rrec->packet->packet.nmb.question.question_name; + struct in_addr answer_ip; + + zero_ip(&answer_ip); + + /* Ensure we don't retry the query but leave the response record cleanup + to the timeout code. We may get more answer responses in which case + we should mark the name in conflict.. */ + rrec->repeat_count = 0; + + if(rrec->num_msgs == 1) + { + /* This is the first response. */ + + if(nmb->header.opcode == NMB_WACK_OPCODE) + { + /* WINS server is telling us to wait. Pretend we didn't get + the response but don't send out any more query requests. */ + + if( DEBUGLVL( 5 ) ) + { + dbgtext( "query_name_response: " ); + dbgtext( "WACK from WINS server %s ", inet_ntoa(p->ip) ); + dbgtext( "in querying name %s ", nmb_namestr(question_name) ); + dbgtext( "on subnet %s.\n", subrec->subnet_name ); + } + + rrec->repeat_count = 0; + /* How long we should wait for. */ + rrec->repeat_time = p->timestamp + nmb->answers->ttl; + rrec->num_msgs--; + return; + } + else if(nmb->header.rcode != 0) + { + success = False; + + if( DEBUGLVL( 5 ) ) + { + dbgtext( "query_name_response: On subnet %s ", subrec->subnet_name ); + dbgtext( "- negative response from IP %s ", inet_ntoa(p->ip) ); + dbgtext( "for name %s. ", nmb_namestr(question_name) ); + dbgtext( "Error code was %d.\n", nmb->header.rcode ); + } + } + else + { + if (!nmb->answers) + { + dbgtext( "query_name_response: On subnet %s ", subrec->subnet_name ); + dbgtext( "IP %s ", inet_ntoa(p->ip) ); + dbgtext( "returned a success response with no answer\n" ); + return; + } + + success = True; + + putip((char *)&answer_ip,&nmb->answers->rdata[2]); + if( DEBUGLVL( 5 ) ) + { + dbgtext( "query_name_response: On subnet %s ", subrec->subnet_name ); + dbgtext( "- positive response from IP %s ", inet_ntoa(p->ip) ); + dbgtext( "for name %s. ", nmb_namestr(question_name) ); + dbgtext( "IP of that name is %s\n", inet_ntoa(answer_ip) ); + } + + /* Interestingly, we could add these names to our namelists, and + change nmbd to a model that checked its own name cache first, + before sending out a query. This is a task for another day, though. + */ + } + } + else if( rrec->num_msgs > 1) + { + if( DEBUGLVL( 0 ) ) + { + if (nmb->answers) + putip( (char *)&answer_ip, &nmb->answers->rdata[2] ); + dbgtext( "query_name_response: " ); + dbgtext( "Multiple (%d) responses ", rrec->num_msgs ); + dbgtext( "received for a query on subnet %s ", subrec->subnet_name ); + dbgtext( "for name %s.\nThis response ", nmb_namestr(question_name) ); + dbgtext( "was from IP %s, reporting ", inet_ntoa(p->ip) ); + dbgtext( "an IP address of %s.\n", inet_ntoa(answer_ip) ); + } + + /* We have already called the success or fail function, so we + don't call again here. Leave the response record around in + case we get more responses. */ + + return; + } + + if(success && rrec->success_fn) + (*(query_name_success_function)rrec->success_fn)(subrec, rrec->userdata, question_name, answer_ip, nmb->answers); + else if( rrec->fail_fn) + (*(query_name_fail_function)rrec->fail_fn)(subrec, rrec, question_name, nmb->header.rcode); + +} + +/**************************************************************************** + Deal with a timeout when querying a name. +****************************************************************************/ + +static void query_name_timeout_response(struct subnet_record *subrec, + struct response_record *rrec) +{ + struct nmb_packet *sent_nmb = &rrec->packet->packet.nmb; + /* We can only fail here, never succeed. */ + BOOL failed = True; + struct nmb_name *question_name = &sent_nmb->question.question_name; + + if(rrec->num_msgs != 0) + { + /* We got at least one response, and have called the success/fail + function already. */ + + failed = False; + } + + if(failed) + { + if( DEBUGLVL( 5 ) ) + { + dbgtext( "query_name_timeout_response: No response to " ); + dbgtext( "query for name %s ", nmb_namestr(question_name) ); + dbgtext( "on subnet %s.\n", subrec->subnet_name ); + } + if(rrec->fail_fn) + (*(query_name_fail_function)rrec->fail_fn)(subrec, rrec, question_name, 0); + } + + remove_response_record(subrec, rrec); +} + +/**************************************************************************** + Lookup a name on our local namelists. We check the lmhosts file first. If the + name is not there we look for the name on the given subnet. +****************************************************************************/ + +static BOOL query_local_namelists(struct subnet_record *subrec, struct nmb_name *nmbname, + struct name_record **namerecp) +{ + struct name_record *namerec; + + *namerecp = NULL; + + if(find_name_in_lmhosts(nmbname, namerecp)) + return True; + + if((namerec = find_name_on_subnet(subrec, nmbname, FIND_ANY_NAME))==NULL) + return False; + + if( NAME_IS_ACTIVE(namerec) + && ( (namerec->data.source == SELF_NAME) + || (namerec->data.source == LMHOSTS_NAME) ) ) + { + *namerecp = namerec; + return True; + } + return False; +} + +/**************************************************************************** + Try and query for a name. +****************************************************************************/ + +BOOL query_name(struct subnet_record *subrec, char *name, int type, + query_name_success_function success_fn, + query_name_fail_function fail_fn, + struct userdata_struct *userdata) +{ + struct nmb_name nmbname; + struct name_record *namerec; + + make_nmb_name(&nmbname, name, type); + + /* + * We need to check our local namelists first. + * It may be an magic name, lmhosts name or just + * a name we have registered. + */ + + if(query_local_namelists(subrec, &nmbname, &namerec) == True) + { + struct res_rec rrec; + int i; + + memset((char *)&rrec, '\0', sizeof(struct res_rec)); + + /* Fake up the needed res_rec just in case it's used. */ + rrec.rr_name = nmbname; + rrec.rr_type = RR_TYPE_NB; + rrec.rr_class = RR_CLASS_IN; + rrec.ttl = PERMANENT_TTL; + rrec.rdlength = namerec->data.num_ips * 6; + if(rrec.rdlength > MAX_DGRAM_SIZE) + { + if( DEBUGLVL( 0 ) ) + { + dbgtext( "query_name: nmbd internal error - " ); + dbgtext( "there are %d ip addresses ", namerec->data.num_ips ); + dbgtext( "for name %s.\n", nmb_namestr(&nmbname) ); + } + return False; + } + + for( i = 0; i < namerec->data.num_ips; i++) + { + set_nb_flags( &rrec.rdata[i*6], namerec->data.nb_flags ); + putip( &rrec.rdata[(i*6) + 2], (char *)&namerec->data.ip[i]); + } + + /* Call the success function directly. */ + if(success_fn) + (*(query_name_success_function)success_fn)(subrec, userdata, &nmbname, namerec->data.ip[0], &rrec); + return False; + } + + if(queue_query_name( subrec, + query_name_response, + query_name_timeout_response, + success_fn, + fail_fn, + userdata, + &nmbname) == NULL) + { + if( DEBUGLVL( 0 ) ) + { + dbgtext( "query_name: Failed to send packet " ); + dbgtext( "trying to query name %s\n", nmb_namestr(&nmbname) ); + } + return True; + } + return False; +} + +/**************************************************************************** + Try and query for a name from nmbd acting as a WINS server. +****************************************************************************/ + +BOOL query_name_from_wins_server(struct in_addr ip_to, + char *name, int type, + query_name_success_function success_fn, + query_name_fail_function fail_fn, + struct userdata_struct *userdata) +{ + struct nmb_name nmbname; + + make_nmb_name(&nmbname, name, type); + + if(queue_query_name_from_wins_server( ip_to, + query_name_response, + query_name_timeout_response, + success_fn, + fail_fn, + userdata, + &nmbname) == NULL) + { + if( DEBUGLVL( 0 ) ) + { + dbgtext( "query_name_from_wins_server: Failed to send packet " ); + dbgtext( "trying to query name %s\n", nmb_namestr(&nmbname) ); + } + return True; + } + return False; +} diff --git a/source4/nmbd/nmbd_nameregister.c b/source4/nmbd/nmbd_nameregister.c new file mode 100644 index 0000000000..7bf2584053 --- /dev/null +++ b/source4/nmbd/nmbd_nameregister.c @@ -0,0 +1,522 @@ +/* + Unix SMB/CIFS implementation. + NBT netbios routines and daemon - version 2 + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Luke Kenneth Casson Leighton 1994-1998 + Copyright (C) Jeremy Allison 1994-1998 + + 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" + +/* forward declarations */ +static void wins_next_registration(struct response_record *rrec); + + +/**************************************************************************** + Deal with a response packet when registering one of our names. +****************************************************************************/ + +static void register_name_response(struct subnet_record *subrec, + struct response_record *rrec, struct packet_struct *p) +{ + /* + * If we are registering broadcast, then getting a response is an + * error - we do not have the name. If we are registering unicast, + * then we expect to get a response. + */ + + struct nmb_packet *nmb = &p->packet.nmb; + BOOL bcast = nmb->header.nm_flags.bcast; + BOOL success = True; + struct nmb_name *question_name = &rrec->packet->packet.nmb.question.question_name; + struct nmb_name *answer_name = &nmb->answers->rr_name; + struct nmb_packet *sent_nmb = &rrec->packet->packet.nmb; + int ttl = 0; + uint16 nb_flags = 0; + struct in_addr register_ip; + fstring reg_name; + + putip(®ister_ip,&sent_nmb->additional->rdata[2]); + fstrcpy(reg_name, inet_ntoa(register_ip)); + + if (subrec == unicast_subnet) { + /* we know that this wins server is definately alive - for the moment! */ + wins_srv_alive(rrec->packet->ip, register_ip); + } + + /* Sanity check. Ensure that the answer name in the incoming packet is the + same as the requested name in the outgoing packet. */ + + if(!question_name || !answer_name) { + DEBUG(0,("register_name_response: malformed response (%s is NULL).\n", + question_name ? "question_name" : "answer_name" )); + return; + } + + if(!nmb_name_equal(question_name, answer_name)) { + DEBUG(0,("register_name_response: Answer name %s differs from question name %s.\n", + nmb_namestr(answer_name), nmb_namestr(question_name))); + return; + } + + if(bcast) { + /* + * Special hack to cope with old Samba nmbd's. + * Earlier versions of Samba (up to 1.9.16p11) respond + * to a broadcast name registration of WORKGROUP<1b> when + * they should not. Hence, until these versions are gone, + * we should treat such errors as success for this particular + * case only. jallison@whistle.com. + */ + +#if 1 /* OLD_SAMBA_SERVER_HACK */ + if((nmb->header.rcode == ACT_ERR) && strequal(lp_workgroup(), answer_name->name) && + (answer_name->name_type == 0x1b)) { + /* Pretend we did not get this. */ + rrec->num_msgs--; + + DEBUG(5,("register_name_response: Ignoring broadcast response to registration of name %s due to old Samba server bug.\n", + nmb_namestr(answer_name))); + return; + } +#endif /* OLD_SAMBA_SERVER_HACK */ + + /* Someone else has the name. Log the problem. */ + DEBUG(1,("register_name_response: Failed to register name %s IP %s on subnet %s via broadcast. Error code was %d. Reject came from IP %s\n", + nmb_namestr(answer_name), + reg_name, + subrec->subnet_name, nmb->header.rcode, inet_ntoa(p->ip))); + success = False; + } else { + /* Unicast - check to see if the response allows us to have the name. */ + if (nmb->header.opcode == NMB_WACK_OPCODE) { + /* WINS server is telling us to wait. Pretend we didn't get + the response but don't send out any more register requests. */ + + DEBUG(5,("register_name_response: WACK from WINS server %s in registering name %s IP %s\n", + inet_ntoa(p->ip), nmb_namestr(answer_name), reg_name)); + + rrec->repeat_count = 0; + /* How long we should wait for. */ + rrec->repeat_time = p->timestamp + nmb->answers->ttl; + rrec->num_msgs--; + return; + } else if (nmb->header.rcode != 0) { + /* Error code - we didn't get the name. */ + success = False; + + DEBUG(0,("register_name_response: %sserver at IP %s rejected our name registration of %s IP %s with error code %d.\n", + subrec==unicast_subnet?"WINS ":"", + inet_ntoa(p->ip), + nmb_namestr(answer_name), + reg_name, + nmb->header.rcode)); + } else { + success = True; + /* Get the data we need to pass to the success function. */ + nb_flags = get_nb_flags(nmb->answers->rdata); + ttl = nmb->answers->ttl; + + /* send off a registration for the next IP, if any */ + wins_next_registration(rrec); + } + } + + DEBUG(5,("register_name_response: %s in registering %sname %s IP %s with %s.\n", + success ? "success" : "failure", + subrec==unicast_subnet?"WINS ":"", + nmb_namestr(answer_name), + reg_name, + inet_ntoa(rrec->packet->ip))); + + if(success) { + /* Enter the registered name into the subnet name database before calling + the success function. */ + standard_success_register(subrec, rrec->userdata, answer_name, nb_flags, ttl, register_ip); + if( rrec->success_fn) + (*(register_name_success_function)rrec->success_fn)(subrec, rrec->userdata, answer_name, nb_flags, ttl, register_ip); + } else { + if( rrec->fail_fn) + (*(register_name_fail_function)rrec->fail_fn)(subrec, rrec, question_name); + /* Remove the name. */ + standard_fail_register( subrec, rrec, question_name); + } + + /* Ensure we don't retry. */ + remove_response_record(subrec, rrec); +} + + +/**************************************************************************** + Deal with a timeout of a WINS registration request +****************************************************************************/ +static void wins_registration_timeout(struct subnet_record *subrec, + struct response_record *rrec) +{ + struct userdata_struct *userdata = rrec->userdata; + struct nmb_packet *sent_nmb = &rrec->packet->packet.nmb; + struct nmb_name *nmbname = &sent_nmb->question.question_name; + struct in_addr register_ip; + fstring src_addr; + + putip(®ister_ip,&sent_nmb->additional->rdata[2]); + + fstrcpy(src_addr, inet_ntoa(register_ip)); + + DEBUG(2,("wins_registration_timeout: WINS server %s timed out registering IP %s\n", + inet_ntoa(rrec->packet->ip), src_addr)); + + /* mark it temporarily dead for this source address */ + wins_srv_died(rrec->packet->ip, register_ip); + + /* if we have some userdata then use that to work out what + wins server to try next */ + if (userdata) { + const char *tag = (const char *)userdata->data; + + /* try the next wins server in our failover list for + this tag */ + rrec->packet->ip = wins_srv_ip_tag(tag, register_ip); + } + + /* if we have run out of wins servers for this tag then they + must all have timed out. We treat this as *success*, not + failure, and go into our standard name refresh mode. This + copes with all the wins servers being down */ + if (wins_srv_is_dead(rrec->packet->ip, register_ip)) { + uint16 nb_flags = get_nb_flags(sent_nmb->additional->rdata); + int ttl = sent_nmb->additional->ttl; + + standard_success_register(subrec, userdata, nmbname, nb_flags, ttl, register_ip); + if(rrec->success_fn) { + (*(register_name_success_function)rrec->success_fn)(subrec, + rrec->userdata, + nmbname, + nb_flags, + ttl, + register_ip); + } + + /* send off a registration for the next IP, if any */ + wins_next_registration(rrec); + + /* don't need to send this packet any more */ + remove_response_record(subrec, rrec); + return; + } + + /* we will be moving to the next WINS server for this group, + send it immediately */ + rrec->repeat_count = 2; + rrec->repeat_time = time(NULL) + 1; + rrec->in_expiration_processing = False; + + DEBUG(6,("Retrying register of name %s IP %s with WINS server %s\n", + nmb_namestr(nmbname), src_addr, inet_ntoa(rrec->packet->ip))); + + /* notice that we don't remove the response record. This keeps + us trying to register with each of our failover wins servers */ +} + + +/**************************************************************************** + Deal with a timeout when registering one of our names. +****************************************************************************/ + +static void register_name_timeout_response(struct subnet_record *subrec, + struct response_record *rrec) +{ + /* + * If we are registering unicast, then NOT getting a response is an + * error - we do not have the name. If we are registering broadcast, + * then we don't expect to get a response. + */ + + struct nmb_packet *sent_nmb = &rrec->packet->packet.nmb; + BOOL bcast = sent_nmb->header.nm_flags.bcast; + BOOL success = False; + struct nmb_name *question_name = &sent_nmb->question.question_name; + uint16 nb_flags = 0; + int ttl = 0; + struct in_addr registered_ip; + + if (bcast) { + if(rrec->num_msgs == 0) { + /* Not receiving a message is success for broadcast registration. */ + success = True; + + /* Pull the success values from the original request packet. */ + nb_flags = get_nb_flags(sent_nmb->additional->rdata); + ttl = sent_nmb->additional->ttl; + putip(®istered_ip,&sent_nmb->additional->rdata[2]); + } + } else { + /* wins timeouts are special */ + wins_registration_timeout(subrec, rrec); + return; + } + + DEBUG(5,("register_name_timeout_response: %s in registering name %s on subnet %s.\n", + success ? "success" : "failure", nmb_namestr(question_name), subrec->subnet_name)); + if(success) { + /* Enter the registered name into the subnet name database before calling + the success function. */ + standard_success_register(subrec, rrec->userdata, question_name, nb_flags, ttl, registered_ip); + if( rrec->success_fn) + (*(register_name_success_function)rrec->success_fn)(subrec, rrec->userdata, question_name, nb_flags, ttl, registered_ip); + } else { + if( rrec->fail_fn) + (*(register_name_fail_function)rrec->fail_fn)(subrec, rrec, question_name); + /* Remove the name. */ + standard_fail_register( subrec, rrec, question_name); + } + + /* Ensure we don't retry. */ + remove_response_record(subrec, rrec); +} + + +/**************************************************************************** +initiate one multi-homed name registration packet +****************************************************************************/ +static void multihomed_register_one(struct nmb_name *nmbname, + uint16 nb_flags, + register_name_success_function success_fn, + register_name_fail_function fail_fn, + struct in_addr ip, + const char *tag) +{ + struct userdata_struct *userdata; + struct in_addr wins_ip = wins_srv_ip_tag(tag, ip); + fstring ip_str; + + userdata = (struct userdata_struct *)malloc(sizeof(*userdata) + strlen(tag) + 1); + if (!userdata) { + DEBUG(0,("Failed to allocate userdata structure!\n")); + return; + } + ZERO_STRUCTP(userdata); + userdata->userdata_len = strlen(tag) + 1; + strlcpy(userdata->data, tag, userdata->userdata_len); + + fstrcpy(ip_str, inet_ntoa(ip)); + + DEBUG(6,("Registering name %s IP %s with WINS server %s using tag '%s'\n", + nmb_namestr(nmbname), ip_str, inet_ntoa(wins_ip), tag)); + + if (queue_register_multihomed_name(unicast_subnet, + register_name_response, + register_name_timeout_response, + success_fn, + fail_fn, + userdata, + nmbname, + nb_flags, + ip, + wins_ip) == NULL) { + DEBUG(0,("multihomed_register_one: Failed to send packet trying to register name %s IP %s\n", + nmb_namestr(nmbname), inet_ntoa(ip))); + } + + free(userdata); +} + + +/**************************************************************************** +we have finished the registration of one IP and need to see if we have +any more IPs left to register with this group of wins server for this name +****************************************************************************/ +static void wins_next_registration(struct response_record *rrec) +{ + struct nmb_packet *sent_nmb = &rrec->packet->packet.nmb; + struct nmb_name *nmbname = &sent_nmb->question.question_name; + uint16 nb_flags = get_nb_flags(sent_nmb->additional->rdata); + struct userdata_struct *userdata = rrec->userdata; + const char *tag; + struct in_addr last_ip; + struct subnet_record *subrec; + + putip(&last_ip,&sent_nmb->additional->rdata[2]); + + if (!userdata) { + /* it wasn't multi-homed */ + return; + } + + tag = (const char *)userdata->data; + + for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) { + if (ip_equal(last_ip, subrec->myip)) { + subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec); + break; + } + } + + if (!subrec) { + /* no more to do! */ + return; + } + + switch (sent_nmb->header.opcode) { + case NMB_NAME_MULTIHOMED_REG_OPCODE: + multihomed_register_one(nmbname, nb_flags, NULL, NULL, subrec->myip, tag); + break; + case NMB_NAME_REFRESH_OPCODE_8: + queue_wins_refresh(nmbname, + register_name_response, + register_name_timeout_response, + nb_flags, subrec->myip, tag); + break; + } +} + +/**************************************************************************** + Try and register one of our names on the unicast subnet - multihomed. +****************************************************************************/ +static void multihomed_register_name(struct nmb_name *nmbname, uint16 nb_flags, + register_name_success_function success_fn, + register_name_fail_function fail_fn) +{ + /* + If we are adding a group name, we just send multiple + register name packets to the WINS server (this is an + internet group name. + + If we are adding a unique name, We need first to add + our names to the unicast subnet namelist. This is + because when a WINS server receives a multihomed + registration request, the first thing it does is to + send a name query to the registering machine, to see + if it has put the name in it's local namelist. + We need the name there so the query response code in + nmbd_incomingrequests.c will find it. + + We are adding this name prematurely (we don't really + have it yet), but as this is on the unicast subnet + only we will get away with this (only the WINS server + will ever query names from us on this subnet). + */ + int num_ips=0; + int i, t; + struct subnet_record *subrec; + char **wins_tags; + struct in_addr *ip_list; + + for(subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec) ) + num_ips++; + + if((ip_list = (struct in_addr *)malloc(num_ips * sizeof(struct in_addr)))==NULL) { + DEBUG(0,("multihomed_register_name: malloc fail !\n")); + return; + } + + for (subrec = FIRST_SUBNET, i = 0; + subrec; + subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec), i++ ) { + ip_list[i] = subrec->myip; + } + + add_name_to_subnet(unicast_subnet, nmbname->name, nmbname->name_type, + nb_flags, lp_max_ttl(), SELF_NAME, + num_ips, ip_list); + + /* get the list of wins tags - we try to register for each of them */ + wins_tags = wins_srv_tags(); + + /* Now try and register the name for each wins tag. Note that + at this point we only register our first IP with each wins + group. We will register the rest from + wins_next_registration() when we get the reply for this + one. That follows the way W2K does things (tridge) + */ + for (t=0; wins_tags && wins_tags[t]; t++) { + multihomed_register_one(nmbname, nb_flags, + success_fn, fail_fn, + ip_list[0], + wins_tags[t]); + } + + wins_srv_tags_free(wins_tags); + + SAFE_FREE(ip_list); +} + + +/**************************************************************************** + Try and register one of our names. +****************************************************************************/ +void register_name(struct subnet_record *subrec, + const char *name, int type, uint16 nb_flags, + register_name_success_function success_fn, + register_name_fail_function fail_fn, + struct userdata_struct *userdata) +{ + struct nmb_name nmbname; + + make_nmb_name(&nmbname, name, type); + + /* Always set the NB_ACTIVE flag on the name we are + registering. Doesn't make sense without it. + */ + + nb_flags |= NB_ACTIVE; + + if (subrec == unicast_subnet) { + /* we now always do multi-homed registration if we are + registering to a WINS server. This copes much + better with complex WINS setups */ + multihomed_register_name(&nmbname, nb_flags, + success_fn, fail_fn); + return; + } + + if (queue_register_name(subrec, + register_name_response, + register_name_timeout_response, + success_fn, + fail_fn, + userdata, + &nmbname, + nb_flags) == NULL) { + DEBUG(0,("register_name: Failed to send packet trying to register name %s\n", + nmb_namestr(&nmbname))); + } +} + + +/**************************************************************************** + Try and refresh one of our names. This is *only* called for WINS refresh +****************************************************************************/ +void wins_refresh_name(struct name_record *namerec) +{ + int t; + char **wins_tags; + + /* get the list of wins tags - we try to refresh for each of them */ + wins_tags = wins_srv_tags(); + + for (t=0; wins_tags && wins_tags[t]; t++) { + queue_wins_refresh(&namerec->name, + register_name_response, + register_name_timeout_response, + namerec->data.nb_flags, + namerec->data.ip[0], wins_tags[t]); + } + + wins_srv_tags_free(wins_tags); +} diff --git a/source4/nmbd/nmbd_namerelease.c b/source4/nmbd/nmbd_namerelease.c new file mode 100644 index 0000000000..0611ca9323 --- /dev/null +++ b/source4/nmbd/nmbd_namerelease.c @@ -0,0 +1,222 @@ +/* + Unix SMB/CIFS implementation. + NBT netbios routines and daemon - version 2 + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Luke Kenneth Casson Leighton 1994-1998 + Copyright (C) Jeremy Allison 1994-1998 + + 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" + +/**************************************************************************** + Deal with a response packet when releasing one of our names. +****************************************************************************/ + +static void release_name_response(struct subnet_record *subrec, + struct response_record *rrec, struct packet_struct *p) +{ + /* + * If we are releasing broadcast, then getting a response is an + * error. If we are releasing unicast, then we expect to get a response. + */ + struct nmb_packet *nmb = &p->packet.nmb; + BOOL bcast = nmb->header.nm_flags.bcast; + BOOL success = True; + struct nmb_name *question_name = &rrec->packet->packet.nmb.question.question_name; + struct nmb_name *answer_name = &nmb->answers->rr_name; + struct in_addr released_ip; + + /* Sanity check. Ensure that the answer name in the incoming packet is the + same as the requested name in the outgoing packet. */ + if (!nmb_name_equal(question_name, answer_name)) { + DEBUG(0,("release_name_response: Answer name %s differs from question name %s.\n", + nmb_namestr(answer_name), nmb_namestr(question_name))); + return; + } + + if (bcast) { + /* Someone sent a response to a bcast release? ignore it. */ + return; + } + + /* Unicast - check to see if the response allows us to release the name. */ + if (nmb->header.rcode != 0) { + /* Error code - we were told not to release the name ! What now ! */ + success = False; + + DEBUG(0,("release_name_response: WINS server at IP %s rejected our \ +name release of name %s with error code %d.\n", + inet_ntoa(p->ip), + nmb_namestr(answer_name), nmb->header.rcode)); + } else if (nmb->header.opcode == NMB_WACK_OPCODE) { + /* WINS server is telling us to wait. Pretend we didn't get + the response but don't send out any more release requests. */ + + DEBUG(5,("release_name_response: WACK from WINS server %s in releasing \ +name %s on subnet %s.\n", + inet_ntoa(p->ip), nmb_namestr(answer_name), subrec->subnet_name)); + + rrec->repeat_count = 0; + /* How long we should wait for. */ + rrec->repeat_time = p->timestamp + nmb->answers->ttl; + rrec->num_msgs--; + return; + } + + DEBUG(5,("release_name_response: %s in releasing name %s on subnet %s.\n", + success ? "success" : "failure", nmb_namestr(answer_name), subrec->subnet_name)); + if (success) { + putip((char*)&released_ip ,&nmb->answers->rdata[2]); + + if(rrec->success_fn) + (*(release_name_success_function)rrec->success_fn)(subrec, rrec->userdata, answer_name, released_ip); + standard_success_release( subrec, rrec->userdata, answer_name, released_ip); + } else { + /* We have no standard_fail_release - maybe we should add one ? */ + if (rrec->fail_fn) { + (*(release_name_fail_function)rrec->fail_fn)(subrec, rrec, answer_name); + } + } + + remove_response_record(subrec, rrec); +} + +/**************************************************************************** + Deal with a timeout when releasing one of our names. +****************************************************************************/ + +static void release_name_timeout_response(struct subnet_record *subrec, + struct response_record *rrec) +{ + /* a release is *always* considered to be successful when it + times out. This doesn't cause problems as if a WINS server + doesn't respond and someone else wants the name then the + normal WACK/name query from the WINS server will cope */ + struct nmb_packet *sent_nmb = &rrec->packet->packet.nmb; + BOOL bcast = sent_nmb->header.nm_flags.bcast; + struct nmb_name *question_name = &sent_nmb->question.question_name; + struct in_addr released_ip; + + /* Get the ip address we were trying to release. */ + putip((char*)&released_ip ,&sent_nmb->additional->rdata[2]); + + if (!bcast) { + /* mark the WINS server temporarily dead */ + wins_srv_died(rrec->packet->ip, released_ip); + } + + DEBUG(5,("release_name_timeout_response: success in releasing name %s on subnet %s.\n", + nmb_namestr(question_name), subrec->subnet_name)); + + if (rrec->success_fn) { + (*(release_name_success_function)rrec->success_fn)(subrec, rrec->userdata, question_name, released_ip); + } + + standard_success_release( subrec, rrec->userdata, question_name, released_ip); + remove_response_record(subrec, rrec); +} + + +/* + when releasing a name with WINS we need to send the release to each of + the WINS groups +*/ +static void wins_release_name(struct name_record *namerec, + release_name_success_function success_fn, + release_name_fail_function fail_fn, + struct userdata_struct *userdata) +{ + int t, i; + char **wins_tags; + + /* get the list of wins tags - we try to release for each of them */ + wins_tags = wins_srv_tags(); + + for (t=0;wins_tags && wins_tags[t]; t++) { + for (i = 0; i < namerec->data.num_ips; i++) { + struct in_addr wins_ip = wins_srv_ip_tag(wins_tags[t], namerec->data.ip[i]); + + BOOL last_one = ((i==namerec->data.num_ips - 1) && !wins_tags[t+1]); + if (queue_release_name(unicast_subnet, + release_name_response, + release_name_timeout_response, + last_one?success_fn : NULL, + last_one? fail_fn : NULL, + last_one? userdata : NULL, + &namerec->name, + namerec->data.nb_flags, + namerec->data.ip[i], + wins_ip) == NULL) { + DEBUG(0,("release_name: Failed to send packet trying to release name %s IP %s\n", + nmb_namestr(&namerec->name), inet_ntoa(namerec->data.ip[i]) )); + } + } + } + + wins_srv_tags_free(wins_tags); +} + + +/**************************************************************************** + Try and release one of our names. +****************************************************************************/ + +void release_name(struct subnet_record *subrec, struct name_record *namerec, + release_name_success_function success_fn, + release_name_fail_function fail_fn, + struct userdata_struct *userdata) +{ + int i; + + /* Ensure it's a SELF name, and in the ACTIVE state. */ + if ((namerec->data.source != SELF_NAME) || !NAME_IS_ACTIVE(namerec)) { + DEBUG(0,("release_name: Cannot release name %s from subnet %s. Source was %d \n", + nmb_namestr(&namerec->name), subrec->subnet_name, namerec->data.source)); + return; + } + + /* Set the name into the deregistering state. */ + namerec->data.nb_flags |= NB_DEREG; + + /* wins releases are a bit different */ + if (subrec == unicast_subnet) { + wins_release_name(namerec, success_fn, fail_fn, userdata); + return; + } + + /* + * Go through and release the name for all known ip addresses. + * Only call the success/fail function on the last one (it should + * only be done once). + */ + for (i = 0; i < namerec->data.num_ips; i++) { + if (queue_release_name(subrec, + release_name_response, + release_name_timeout_response, + (i == (namerec->data.num_ips - 1)) ? success_fn : NULL, + (i == (namerec->data.num_ips - 1)) ? fail_fn : NULL, + (i == (namerec->data.num_ips - 1)) ? userdata : NULL, + &namerec->name, + namerec->data.nb_flags, + namerec->data.ip[i], + subrec->bcast_ip) == NULL) { + DEBUG(0,("release_name: Failed to send packet trying to release name %s IP %s\n", + nmb_namestr(&namerec->name), inet_ntoa(namerec->data.ip[i]) )); + } + } +} diff --git a/source4/nmbd/nmbd_nodestatus.c b/source4/nmbd/nmbd_nodestatus.c new file mode 100644 index 0000000000..993e4d9d17 --- /dev/null +++ b/source4/nmbd/nmbd_nodestatus.c @@ -0,0 +1,94 @@ +/* + Unix SMB/CIFS implementation. + NBT netbios routines and daemon - version 2 + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Luke Kenneth Casson Leighton 1994-1998 + Copyright (C) Jeremy Allison 1994-1998 + + 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" + +/**************************************************************************** + Deal with a successful node status response. +****************************************************************************/ +static void node_status_response(struct subnet_record *subrec, + struct response_record *rrec, struct packet_struct *p) +{ + struct nmb_packet *nmb = &p->packet.nmb; + struct nmb_name *question_name = &rrec->packet->packet.nmb.question.question_name; + struct nmb_name *answer_name = &nmb->answers->rr_name; + + /* Sanity check. Ensure that the answer name in the incoming packet is the + same as the requested name in the outgoing packet. */ + + if(!nmb_name_equal(question_name, answer_name)) + { + DEBUG(0,("node_status_response: Answer name %s differs from question \ +name %s.\n", nmb_namestr(answer_name), nmb_namestr(question_name))); + return; + } + + DEBUG(5,("node_status_response: response from name %s on subnet %s.\n", + nmb_namestr(answer_name), subrec->subnet_name)); + + /* Just send the whole answer resource record for the success function + to parse. */ + if(rrec->success_fn) + (*(node_status_success_function)rrec->success_fn)(subrec, rrec->userdata, nmb->answers, p->ip); + + /* Ensure we don't retry. */ + remove_response_record(subrec, rrec); +} + +/**************************************************************************** + Deal with a timeout when requesting a node status. +****************************************************************************/ +static void node_status_timeout_response(struct subnet_record *subrec, + struct response_record *rrec) +{ + struct nmb_packet *sent_nmb = &rrec->packet->packet.nmb; + struct nmb_name *question_name = &sent_nmb->question.question_name; + + DEBUG(5,("node_status_timeout_response: failed to get node status from name %s on subnet %s\n", + nmb_namestr(question_name), subrec->subnet_name)); + + if( rrec->fail_fn) + (*rrec->fail_fn)(subrec, rrec); + + /* Ensure we don't retry. */ + remove_response_record(subrec, rrec); +} + +/**************************************************************************** + Try and do a node status to a name - given the name & IP address. +****************************************************************************/ + +BOOL node_status(struct subnet_record *subrec, struct nmb_name *nmbname, + struct in_addr send_ip, node_status_success_function success_fn, + node_status_fail_function fail_fn, struct userdata_struct *userdata) +{ + if(queue_node_status( subrec, + node_status_response, node_status_timeout_response, + success_fn, fail_fn, userdata, nmbname, send_ip)==NULL) + { + DEBUG(0,("node_status: Failed to send packet trying to get node status for \ +name %s, IP address %s\n", nmb_namestr(nmbname), inet_ntoa(send_ip))); + return True; + } + return False; +} diff --git a/source4/nmbd/nmbd_packets.c b/source4/nmbd/nmbd_packets.c new file mode 100644 index 0000000000..6ee13812dc --- /dev/null +++ b/source4/nmbd/nmbd_packets.c @@ -0,0 +1,2013 @@ +/* + Unix SMB/CIFS implementation. + NBT netbios routines and daemon - version 2 + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Luke Kenneth Casson Leighton 1994-1998 + Copyright (C) Jeremy Allison 1994-1998 + + 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" + +extern int ClientNMB; +extern int ClientDGRAM; +extern int global_nmb_port; + +extern int num_response_packets; + +extern struct in_addr loopback_ip; + +static void queue_packet(struct packet_struct *packet); + +BOOL rescan_listen_set = False; + + +/******************************************************************* + The global packet linked-list. Incoming entries are + added to the end of this list. It is supposed to remain fairly + short so we won't bother with an end pointer. +******************************************************************/ + +static struct packet_struct *packet_queue = NULL; + +/*************************************************************************** +Utility function to find the specific fd to send a packet out on. +**************************************************************************/ + +static int find_subnet_fd_for_address( struct in_addr local_ip ) +{ + struct subnet_record *subrec; + + for( subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) + if(ip_equal(local_ip, subrec->myip)) + return subrec->nmb_sock; + + return ClientNMB; +} + +/*************************************************************************** +Utility function to find the specific fd to send a mailslot packet out on. +**************************************************************************/ + +static int find_subnet_mailslot_fd_for_address( struct in_addr local_ip ) +{ + struct subnet_record *subrec; + + for( subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) + if(ip_equal(local_ip, subrec->myip)) + return subrec->dgram_sock; + + return ClientDGRAM; +} + +/*************************************************************************** +Get/Set problematic nb_flags as network byte order 16 bit int. +**************************************************************************/ + +uint16 get_nb_flags(char *buf) +{ + return ((((uint16)*buf)&0xFFFF) & NB_FLGMSK); +} + +void set_nb_flags(char *buf, uint16 nb_flags) +{ + *buf++ = ((nb_flags & NB_FLGMSK) & 0xFF); + *buf = '\0'; +} + +/*************************************************************************** +Dumps out the browse packet data. +**************************************************************************/ + +static void debug_browse_data(char *outbuf, int len) +{ + int i,j; + + DEBUG( 4, ( "debug_browse_data():\n" ) ); + for (i = 0; i < len; i+= 16) + { + DEBUGADD( 4, ( "%3x char ", i ) ); + + for (j = 0; j < 16; j++) + { + unsigned char x; + if (i+j >= len) + break; + + x = outbuf[i+j]; + if (x < 32 || x > 127) + x = '.'; + + DEBUGADD( 4, ( "%c", x ) ); + } + + DEBUGADD( 4, ( "%*s hex", 16-j, "" ) ); + + for (j = 0; j < 16; j++) + { + if (i+j >= len) + break; + DEBUGADD( 4, ( " %02x", (unsigned char)outbuf[i+j] ) ); + } + + DEBUGADD( 4, ("\n") ); + } +} + +/*************************************************************************** + Generates the unique transaction identifier +**************************************************************************/ + +static uint16 name_trn_id=0; + +static uint16 generate_name_trn_id(void) +{ + + if (!name_trn_id) + { + name_trn_id = ((unsigned)time(NULL)%(unsigned)0x7FFF) + ((unsigned)sys_getpid()%(unsigned)100); + } + name_trn_id = (name_trn_id+1) % (unsigned)0x7FFF; + return name_trn_id; +} + +/*************************************************************************** + Either loops back or sends out a completed NetBIOS packet. +**************************************************************************/ + +static BOOL send_netbios_packet(struct packet_struct *p) +{ + BOOL loopback_this_packet = False; + + /* Check if we are sending to or from ourselves as a WINS server. */ + if(ismyip(p->ip) && (p->port == global_nmb_port)) + loopback_this_packet = True; + + if(loopback_this_packet) + { + struct packet_struct *lo_packet = NULL; + DEBUG(5,("send_netbios_packet: sending packet to ourselves.\n")); + if((lo_packet = copy_packet(p)) == NULL) + return False; + queue_packet(lo_packet); + } + else if (!send_packet(p)) + { + DEBUG(0,("send_netbios_packet: send_packet() to IP %s port %d failed\n", + inet_ntoa(p->ip),p->port)); + return False; + } + + return True; +} + +/*************************************************************************** + Sets up the common elements of an outgoing NetBIOS packet. + + Note: do not attempt to rationalise whether rec_des should be set or not + in a particular situation. Just follow rfc_1002 or look at examples from WinXX. + It does NOT follow the rule that requests to the wins server always have + rec_des true. See for example name releases and refreshes +**************************************************************************/ + +static struct packet_struct *create_and_init_netbios_packet(struct nmb_name *nmbname, + BOOL bcast, BOOL rec_des, + struct in_addr to_ip) +{ + struct packet_struct *packet = NULL; + struct nmb_packet *nmb = NULL; + + /* Allocate the packet_struct we will return. */ + if((packet = (struct packet_struct *)malloc(sizeof(*packet))) == NULL) + { + DEBUG(0,("create_and_init_netbios_packet: malloc fail (1) for packet struct.\n")); + return NULL; + } + + memset((char *)packet,'\0',sizeof(*packet)); + + nmb = &packet->packet.nmb; + + nmb->header.name_trn_id = generate_name_trn_id(); + nmb->header.response = False; + nmb->header.nm_flags.recursion_desired = rec_des; + nmb->header.nm_flags.recursion_available = False; + nmb->header.nm_flags.trunc = False; + nmb->header.nm_flags.authoritative = False; + nmb->header.nm_flags.bcast = bcast; + + nmb->header.rcode = 0; + nmb->header.qdcount = 1; + nmb->header.ancount = 0; + nmb->header.nscount = 0; + + nmb->question.question_name = *nmbname; + nmb->question.question_type = QUESTION_TYPE_NB_QUERY; + nmb->question.question_class = QUESTION_CLASS_IN; + + packet->ip = to_ip; + packet->port = NMB_PORT; + packet->fd = ClientNMB; + packet->timestamp = time(NULL); + packet->packet_type = NMB_PACKET; + packet->locked = False; + + return packet; /* Caller must free. */ +} + +/*************************************************************************** + Sets up the common elements of register, refresh or release packet. +**************************************************************************/ + +static BOOL create_and_init_additional_record(struct packet_struct *packet, + uint16 nb_flags, + struct in_addr *register_ip) +{ + struct nmb_packet *nmb = &packet->packet.nmb; + + if((nmb->additional = (struct res_rec *)malloc(sizeof(struct res_rec))) == NULL) { + DEBUG(0,("initiate_name_register_packet: malloc fail for additional record.\n")); + return False; + } + + memset((char *)nmb->additional,'\0',sizeof(struct res_rec)); + + nmb->additional->rr_name = nmb->question.question_name; + nmb->additional->rr_type = RR_TYPE_NB; + nmb->additional->rr_class = RR_CLASS_IN; + + /* See RFC 1002, sections 5.1.1.1, 5.1.1.2 and 5.1.1.3 */ + if (nmb->header.nm_flags.bcast) + nmb->additional->ttl = PERMANENT_TTL; + else + nmb->additional->ttl = lp_max_ttl(); + + nmb->additional->rdlength = 6; + + set_nb_flags(nmb->additional->rdata,nb_flags); + + /* Set the address for the name we are registering. */ + putip(&nmb->additional->rdata[2], register_ip); + + /* + it turns out that Jeremys code was correct, we are supposed + to send registrations from the IP we are registering. The + trick is what to do on timeouts! When we send on a + non-routable IP then the reply will timeout, and we should + treat this as success, not failure. That means we go into + our standard refresh cycle for that name which copes nicely + with disconnected networks. + */ + packet->fd = find_subnet_fd_for_address(*register_ip); + + return True; +} + +/*************************************************************************** + Sends out a name query. +**************************************************************************/ + +static BOOL initiate_name_query_packet( struct packet_struct *packet) +{ + struct nmb_packet *nmb = NULL; + + nmb = &packet->packet.nmb; + + nmb->header.opcode = NMB_NAME_QUERY_OPCODE; + nmb->header.arcount = 0; + + nmb->header.nm_flags.recursion_desired = True; + + DEBUG(4,("initiate_name_query_packet: sending query for name %s (bcast=%s) to IP %s\n", + nmb_namestr(&nmb->question.question_name), + BOOLSTR(nmb->header.nm_flags.bcast), inet_ntoa(packet->ip))); + + return send_netbios_packet( packet ); +} + +/*************************************************************************** + Sends out a name query - from a WINS server. +**************************************************************************/ + +static BOOL initiate_name_query_packet_from_wins_server( struct packet_struct *packet) +{ + struct nmb_packet *nmb = NULL; + + nmb = &packet->packet.nmb; + + nmb->header.opcode = NMB_NAME_QUERY_OPCODE; + nmb->header.arcount = 0; + + nmb->header.nm_flags.recursion_desired = False; + + DEBUG(4,("initiate_name_query_packet_from_wins_server: sending query for name %s (bcast=%s) to IP %s\n", + nmb_namestr(&nmb->question.question_name), + BOOLSTR(nmb->header.nm_flags.bcast), inet_ntoa(packet->ip))); + + return send_netbios_packet( packet ); +} + +/*************************************************************************** + Sends out a name register. +**************************************************************************/ + +static BOOL initiate_name_register_packet( struct packet_struct *packet, + uint16 nb_flags, struct in_addr *register_ip) +{ + struct nmb_packet *nmb = &packet->packet.nmb; + + nmb->header.opcode = NMB_NAME_REG_OPCODE; + nmb->header.arcount = 1; + + nmb->header.nm_flags.recursion_desired = True; + + if(create_and_init_additional_record(packet, nb_flags, register_ip) == False) + return False; + + DEBUG(4,("initiate_name_register_packet: sending registration for name %s (bcast=%s) to IP %s\n", + nmb_namestr(&nmb->additional->rr_name), + BOOLSTR(nmb->header.nm_flags.bcast), inet_ntoa(packet->ip))); + + return send_netbios_packet( packet ); +} + +/*************************************************************************** + Sends out a multihomed name register. +**************************************************************************/ + +static BOOL initiate_multihomed_name_register_packet(struct packet_struct *packet, + uint16 nb_flags, struct in_addr *register_ip) +{ + struct nmb_packet *nmb = &packet->packet.nmb; + fstring second_ip_buf; + + fstrcpy(second_ip_buf, inet_ntoa(packet->ip)); + + nmb->header.opcode = NMB_NAME_MULTIHOMED_REG_OPCODE; + nmb->header.arcount = 1; + + nmb->header.nm_flags.recursion_desired = True; + + if(create_and_init_additional_record(packet, nb_flags, register_ip) == False) + return False; + + DEBUG(4,("initiate_multihomed_name_register_packet: sending registration \ +for name %s IP %s (bcast=%s) to IP %s\n", + nmb_namestr(&nmb->additional->rr_name), inet_ntoa(*register_ip), + BOOLSTR(nmb->header.nm_flags.bcast), second_ip_buf )); + + return send_netbios_packet( packet ); +} + +/*************************************************************************** + Sends out a name refresh. +**************************************************************************/ + +static BOOL initiate_name_refresh_packet( struct packet_struct *packet, + uint16 nb_flags, struct in_addr *refresh_ip) +{ + struct nmb_packet *nmb = &packet->packet.nmb; + + nmb->header.opcode = NMB_NAME_REFRESH_OPCODE_8; + nmb->header.arcount = 1; + + nmb->header.nm_flags.recursion_desired = False; + + if(create_and_init_additional_record(packet, nb_flags, refresh_ip) == False) + return False; + + DEBUG(4,("initiate_name_refresh_packet: sending refresh for name %s (bcast=%s) to IP %s\n", + nmb_namestr(&nmb->additional->rr_name), + BOOLSTR(nmb->header.nm_flags.bcast), inet_ntoa(packet->ip))); + + return send_netbios_packet( packet ); +} + +/*************************************************************************** + Sends out a name release. +**************************************************************************/ + +static BOOL initiate_name_release_packet( struct packet_struct *packet, + uint16 nb_flags, struct in_addr *release_ip) +{ + struct nmb_packet *nmb = &packet->packet.nmb; + + nmb->header.opcode = NMB_NAME_RELEASE_OPCODE; + nmb->header.arcount = 1; + + nmb->header.nm_flags.recursion_desired = False; + + if(create_and_init_additional_record(packet, nb_flags, release_ip) == False) + return False; + + DEBUG(4,("initiate_name_release_packet: sending release for name %s (bcast=%s) to IP %s\n", + nmb_namestr(&nmb->additional->rr_name), + BOOLSTR(nmb->header.nm_flags.bcast), inet_ntoa(packet->ip))); + + return send_netbios_packet( packet ); +} + +/*************************************************************************** + Sends out a node status. +**************************************************************************/ + +static BOOL initiate_node_status_packet( struct packet_struct *packet ) +{ + struct nmb_packet *nmb = &packet->packet.nmb; + + nmb->header.opcode = NMB_NAME_QUERY_OPCODE; + nmb->header.arcount = 0; + + nmb->header.nm_flags.recursion_desired = False; + + nmb->question.question_type = QUESTION_TYPE_NB_STATUS; + + DEBUG(4,("initiate_node_status_packet: sending node status request for name %s to IP %s\n", + nmb_namestr(&nmb->question.question_name), + inet_ntoa(packet->ip))); + + return send_netbios_packet( packet ); +} + +/**************************************************************************** + Simplification functions for queuing standard packets. + These should be the only publicly callable functions for sending + out packets. +****************************************************************************/ + +/**************************************************************************** + Assertion - we should never be sending nmbd packets on the remote + broadcast subnet. +****************************************************************************/ + +static BOOL assert_check_subnet(struct subnet_record *subrec) +{ + if( subrec == remote_broadcast_subnet) + { + DEBUG(0,("assert_check_subnet: Attempt to send packet on remote broadcast subnet. \ +This is a bug.\n")); + return True; + } + return False; +} + +/**************************************************************************** + Queue a register name packet to the broadcast address of a subnet. +****************************************************************************/ + +struct response_record *queue_register_name( struct subnet_record *subrec, + response_function resp_fn, + timeout_response_function timeout_fn, + register_name_success_function success_fn, + register_name_fail_function fail_fn, + struct userdata_struct *userdata, + struct nmb_name *nmbname, + uint16 nb_flags) +{ + struct packet_struct *p; + struct response_record *rrec; + + if(assert_check_subnet(subrec)) + return NULL; + + /* note that all name registration requests have RD set (rfc1002 - + section 4.2.2 */ + if ((p = create_and_init_netbios_packet(nmbname, (subrec != unicast_subnet), True, + subrec->bcast_ip)) == NULL) + return NULL; + + if(initiate_name_register_packet( p, nb_flags, + iface_ip(subrec->bcast_ip)) == False) + { + p->locked = False; + free_packet(p); + return NULL; + } + + if((rrec = make_response_record(subrec, /* subnet record. */ + p, /* packet we sent. */ + resp_fn, /* function to call on response. */ + timeout_fn, /* function to call on timeout. */ + (success_function)success_fn, /* function to call on operation success. */ + (fail_function)fail_fn, /* function to call on operation fail. */ + userdata)) == NULL) + { + p->locked = False; + free_packet(p); + return NULL; + } + + return rrec; +} + + +/**************************************************************************** + Queue a refresh name packet to the broadcast address of a subnet. +****************************************************************************/ +void queue_wins_refresh(struct nmb_name *nmbname, + response_function resp_fn, + timeout_response_function timeout_fn, + uint16 nb_flags, + struct in_addr refresh_ip, + const char *tag) +{ + struct packet_struct *p; + struct response_record *rrec; + struct in_addr wins_ip; + struct userdata_struct *userdata; + fstring ip_str; + + wins_ip = wins_srv_ip_tag(tag, refresh_ip); + + if ((p = create_and_init_netbios_packet(nmbname, False, False, wins_ip)) == NULL) { + return; + } + + if (!initiate_name_refresh_packet(p, nb_flags, &refresh_ip)) { + p->locked = False; + free_packet(p); + return; + } + + fstrcpy(ip_str, inet_ntoa(refresh_ip)); + + DEBUG(6,("Refreshing name %s IP %s with WINS server %s using tag '%s'\n", + nmb_namestr(nmbname), ip_str, inet_ntoa(wins_ip), tag)); + + userdata = (struct userdata_struct *)malloc(sizeof(*userdata) + strlen(tag) + 1); + if (!userdata) { + DEBUG(0,("Failed to allocate userdata structure!\n")); + return; + } + ZERO_STRUCTP(userdata); + userdata->userdata_len = strlen(tag) + 1; + strlcpy(userdata->data, tag, userdata->userdata_len); + + if ((rrec = make_response_record(unicast_subnet, + p, + resp_fn, timeout_fn, + NULL, + NULL, + userdata)) == NULL) { + p->locked = False; + free_packet(p); + return; + } + + free(userdata); + + /* we don't want to repeat refresh packets */ + rrec->repeat_count = 0; +} + + +/**************************************************************************** + Queue a multihomed register name packet to a given WINS server IP +****************************************************************************/ + +struct response_record *queue_register_multihomed_name( struct subnet_record *subrec, + response_function resp_fn, + timeout_response_function timeout_fn, + register_name_success_function success_fn, + register_name_fail_function fail_fn, + struct userdata_struct *userdata, + struct nmb_name *nmbname, + uint16 nb_flags, + struct in_addr register_ip, + struct in_addr wins_ip) +{ + struct packet_struct *p; + struct response_record *rrec; + BOOL ret; + + /* Sanity check. */ + if(subrec != unicast_subnet) { + DEBUG(0,("queue_register_multihomed_name: should only be done on \ +unicast subnet. subnet is %s\n.", subrec->subnet_name )); + return NULL; + } + + if(assert_check_subnet(subrec)) + return NULL; + + if ((p = create_and_init_netbios_packet(nmbname, False, True, wins_ip)) == NULL) + return NULL; + + if (nb_flags & NB_GROUP) + ret = initiate_name_register_packet( p, nb_flags, ®ister_ip); + else + ret = initiate_multihomed_name_register_packet(p, nb_flags, ®ister_ip); + + if (ret == False) { + p->locked = False; + free_packet(p); + return NULL; + } + + if ((rrec = make_response_record(subrec, /* subnet record. */ + p, /* packet we sent. */ + resp_fn, /* function to call on response. */ + timeout_fn, /* function to call on timeout. */ + (success_function)success_fn, /* function to call on operation success. */ + (fail_function)fail_fn, /* function to call on operation fail. */ + userdata)) == NULL) { + p->locked = False; + free_packet(p); + return NULL; + } + + return rrec; +} + +/**************************************************************************** + Queue a release name packet to the broadcast address of a subnet. +****************************************************************************/ + +struct response_record *queue_release_name( struct subnet_record *subrec, + response_function resp_fn, + timeout_response_function timeout_fn, + release_name_success_function success_fn, + release_name_fail_function fail_fn, + struct userdata_struct *userdata, + struct nmb_name *nmbname, + uint16 nb_flags, + struct in_addr release_ip, + struct in_addr dest_ip) +{ + struct packet_struct *p; + struct response_record *rrec; + + if(assert_check_subnet(subrec)) + return NULL; + + if ((p = create_and_init_netbios_packet(nmbname, (subrec != unicast_subnet), False, + dest_ip)) == NULL) + return NULL; + + if(initiate_name_release_packet( p, nb_flags, &release_ip) == False) + { + p->locked = False; + free_packet(p); + return NULL; + } + + if((rrec = make_response_record(subrec, /* subnet record. */ + p, /* packet we sent. */ + resp_fn, /* function to call on response. */ + timeout_fn, /* function to call on timeout. */ + (success_function)success_fn, /* function to call on operation success. */ + (fail_function)fail_fn, /* function to call on operation fail. */ + userdata)) == NULL) + { + p->locked = False; + free_packet(p); + return NULL; + } + + /* + * For a broadcast release packet, only send once. + * This will cause us to remove the name asap. JRA. + */ + + if (subrec != unicast_subnet) { + rrec->repeat_count = 0; + rrec->repeat_time = 0; + } + + return rrec; +} + +/**************************************************************************** + Queue a query name packet to the broadcast address of a subnet. +****************************************************************************/ + +struct response_record *queue_query_name( struct subnet_record *subrec, + response_function resp_fn, + timeout_response_function timeout_fn, + query_name_success_function success_fn, + query_name_fail_function fail_fn, + struct userdata_struct *userdata, + struct nmb_name *nmbname) +{ + struct packet_struct *p; + struct response_record *rrec; + struct in_addr to_ip; + + if(assert_check_subnet(subrec)) + return NULL; + + to_ip = subrec->bcast_ip; + + /* queries to the WINS server turn up here as queries to IP 0.0.0.0 + These need to be handled a bit differently */ + if (subrec->type == UNICAST_SUBNET && is_zero_ip(to_ip)) { + /* what we really need to do is loop over each of our wins + * servers and wins server tags here, but that just doesn't + * fit our architecture at the moment (userdata may already + * be used when we get here). For now we just query the first + * active wins server on the first tag. */ + char **tags = wins_srv_tags(); + if (!tags) { + return NULL; + } + to_ip = wins_srv_ip_tag(tags[0], to_ip); + wins_srv_tags_free(tags); + } + + if(( p = create_and_init_netbios_packet(nmbname, + (subrec != unicast_subnet), + (subrec == unicast_subnet), + to_ip)) == NULL) + return NULL; + + if(lp_bind_interfaces_only()) { + int i; + + DEBUG(10,("queue_query_name: bind_interfaces_only is set, looking for suitable source IP\n")); + for(i = 0; i < iface_count(); i++) { + struct in_addr *ifip = iface_n_ip(i); + + if(ifip == NULL) { + DEBUG(0,("queue_query_name: interface %d has NULL IP address !\n", i)); + continue; + } + + if (ip_equal(*ifip,loopback_ip)) { + DEBUG(5,("queue_query_name: ignoring loopback interface (%d)\n", i)); + continue; + } + + DEBUG(10,("queue_query_name: using source IP %s\n",inet_ntoa(*ifip))); + p->fd = find_subnet_fd_for_address( *ifip ); + break; + } + } + + if(initiate_name_query_packet( p ) == False) { + p->locked = False; + free_packet(p); + return NULL; + } + + if((rrec = make_response_record(subrec, /* subnet record. */ + p, /* packet we sent. */ + resp_fn, /* function to call on response. */ + timeout_fn, /* function to call on timeout. */ + (success_function)success_fn, /* function to call on operation success. */ + (fail_function)fail_fn, /* function to call on operation fail. */ + userdata)) == NULL) + { + p->locked = False; + free_packet(p); + return NULL; + } + + return rrec; +} + +/**************************************************************************** + Queue a query name packet to a given address from the WINS subnet. +****************************************************************************/ + +struct response_record *queue_query_name_from_wins_server( struct in_addr to_ip, + response_function resp_fn, + timeout_response_function timeout_fn, + query_name_success_function success_fn, + query_name_fail_function fail_fn, + struct userdata_struct *userdata, + struct nmb_name *nmbname) +{ + struct packet_struct *p; + struct response_record *rrec; + + if ((p = create_and_init_netbios_packet(nmbname, False, False, to_ip)) == NULL) + return NULL; + + if(initiate_name_query_packet_from_wins_server( p ) == False) + { + p->locked = False; + free_packet(p); + return NULL; + } + + if((rrec = make_response_record(wins_server_subnet, /* subnet record. */ + p, /* packet we sent. */ + resp_fn, /* function to call on response. */ + timeout_fn, /* function to call on timeout. */ + (success_function)success_fn, /* function to call on operation success. */ + (fail_function)fail_fn, /* function to call on operation fail. */ + userdata)) == NULL) + { + p->locked = False; + free_packet(p); + return NULL; + } + + return rrec; +} + +/**************************************************************************** + Queue a node status packet to a given name and address. +****************************************************************************/ + +struct response_record *queue_node_status( struct subnet_record *subrec, + response_function resp_fn, + timeout_response_function timeout_fn, + node_status_success_function success_fn, + node_status_fail_function fail_fn, + struct userdata_struct *userdata, + struct nmb_name *nmbname, + struct in_addr send_ip) +{ + struct packet_struct *p; + struct response_record *rrec; + + /* Sanity check. */ + if(subrec != unicast_subnet) + { + DEBUG(0,("queue_register_multihomed_name: should only be done on \ +unicast subnet. subnet is %s\n.", subrec->subnet_name )); + return NULL; + } + + if(assert_check_subnet(subrec)) + return NULL; + + if(( p = create_and_init_netbios_packet(nmbname, False, False, + send_ip)) == NULL) + return NULL; + + if(initiate_node_status_packet(p) == False) + { + p->locked = False; + free_packet(p); + return NULL; + } + + if((rrec = make_response_record(subrec, /* subnet record. */ + p, /* packet we sent. */ + resp_fn, /* function to call on response. */ + timeout_fn, /* function to call on timeout. */ + (success_function)success_fn, /* function to call on operation success. */ + (fail_function)fail_fn, /* function to call on operation fail. */ + userdata)) == NULL) + { + p->locked = False; + free_packet(p); + return NULL; + } + + return rrec; +} + +/**************************************************************************** + Reply to a netbios name packet. see rfc1002.txt +****************************************************************************/ + +void reply_netbios_packet(struct packet_struct *orig_packet, + int rcode, enum netbios_reply_type_code rcv_code, int opcode, + int ttl, char *data,int len) +{ + struct packet_struct packet; + struct nmb_packet *nmb = NULL; + struct res_rec answers; + struct nmb_packet *orig_nmb = &orig_packet->packet.nmb; + BOOL loopback_this_packet = False; + const char *packet_type = "unknown"; + + /* Check if we are sending to or from ourselves. */ + if(ismyip(orig_packet->ip) && (orig_packet->port == global_nmb_port)) + loopback_this_packet = True; + + nmb = &packet.packet.nmb; + + /* Do a partial copy of the packet. We clear the locked flag and + the resource record pointers. */ + packet = *orig_packet; /* Full structure copy. */ + packet.locked = False; + nmb->answers = NULL; + nmb->nsrecs = NULL; + nmb->additional = NULL; + + switch (rcv_code) + { + case NMB_STATUS: + { + packet_type = "nmb_status"; + nmb->header.nm_flags.recursion_desired = False; + nmb->header.nm_flags.recursion_available = False; + break; + } + case NMB_QUERY: + { + packet_type = "nmb_query"; + nmb->header.nm_flags.recursion_desired = True; + nmb->header.nm_flags.recursion_available = True; + break; + } + case NMB_REG: + case NMB_REG_REFRESH: + { + packet_type = "nmb_reg"; + nmb->header.nm_flags.recursion_desired = True; + nmb->header.nm_flags.recursion_available = True; + break; + } + case NMB_REL: + { + packet_type = "nmb_rel"; + nmb->header.nm_flags.recursion_desired = False; + nmb->header.nm_flags.recursion_available = False; + break; + } + case NMB_WAIT_ACK: + { + packet_type = "nmb_wack"; + nmb->header.nm_flags.recursion_desired = False; + nmb->header.nm_flags.recursion_available = False; + break; + } + case WINS_REG: + { + packet_type = "wins_reg"; + nmb->header.nm_flags.recursion_desired = True; + nmb->header.nm_flags.recursion_available = True; + break; + } + case WINS_QUERY: + { + packet_type = "wins_query"; + nmb->header.nm_flags.recursion_desired = True; + nmb->header.nm_flags.recursion_available = True; + break; + } + + default: + { + DEBUG(0,("reply_netbios_packet: Unknown packet type: %s %s to ip %s\n", + packet_type, nmb_namestr(&orig_nmb->question.question_name), + inet_ntoa(packet.ip))); + + return; + } + } + + DEBUG(4,("reply_netbios_packet: sending a reply of packet type: %s %s to ip %s \ +for id %hu\n", + packet_type, nmb_namestr(&orig_nmb->question.question_name), + inet_ntoa(packet.ip), orig_nmb->header.name_trn_id)); + + nmb->header.name_trn_id = orig_nmb->header.name_trn_id; + nmb->header.opcode = opcode; + nmb->header.response = True; + nmb->header.nm_flags.bcast = False; + nmb->header.nm_flags.trunc = False; + nmb->header.nm_flags.authoritative = True; + + nmb->header.rcode = rcode; + nmb->header.qdcount = 0; + nmb->header.ancount = 1; + nmb->header.nscount = 0; + nmb->header.arcount = 0; + + memset((char*)&nmb->question,'\0',sizeof(nmb->question)); + + nmb->answers = &answers; + memset((char*)nmb->answers,'\0',sizeof(*nmb->answers)); + + nmb->answers->rr_name = orig_nmb->question.question_name; + nmb->answers->rr_type = orig_nmb->question.question_type; + nmb->answers->rr_class = orig_nmb->question.question_class; + nmb->answers->ttl = ttl; + + if (data && len) + { + nmb->answers->rdlength = len; + memcpy(nmb->answers->rdata, data, len); + } + + packet.packet_type = NMB_PACKET; + /* Ensure we send out on the same fd that the original + packet came in on to give the correct source IP address. */ + packet.fd = orig_packet->fd; + packet.timestamp = time(NULL); + + debug_nmb_packet(&packet); + + if(loopback_this_packet) + { + struct packet_struct *lo_packet; + DEBUG(5,("reply_netbios_packet: sending packet to ourselves.\n")); + if((lo_packet = copy_packet(&packet)) == NULL) + return; + queue_packet(lo_packet); + } + else if (!send_packet(&packet)) + { + DEBUG(0,("reply_netbios_packet: send_packet to IP %s port %d failed\n", + inet_ntoa(packet.ip),packet.port)); + } +} + +/******************************************************************* + Queue a packet into a packet queue +******************************************************************/ +static void queue_packet(struct packet_struct *packet) +{ + struct packet_struct *p; + + if (!packet_queue) + { + packet->prev = NULL; + packet->next = NULL; + packet_queue = packet; + return; + } + + /* find the bottom */ + for (p=packet_queue;p->next;p=p->next) + ; + + p->next = packet; + packet->next = NULL; + packet->prev = p; +} + +/**************************************************************************** + Try and find a matching subnet record for a datagram port 138 packet. +****************************************************************************/ + +static struct subnet_record *find_subnet_for_dgram_browse_packet(struct packet_struct *p) +{ + struct subnet_record *subrec; + + /* Go through all the broadcast subnets and see if the mask matches. */ + for (subrec = FIRST_SUBNET; subrec ; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) + { + if(same_net(p->ip, subrec->bcast_ip, subrec->mask_ip)) + return subrec; + } + + /* If the subnet record is the remote announce broadcast subnet, + hack it here to be the first subnet. This is really gross and + is needed due to people turning on port 137/138 broadcast + forwarding on their routers. May fire and brimstone rain + down upon them... + */ + + return FIRST_SUBNET; +} + +/**************************************************************************** +Dispatch a browse frame from port 138 to the correct processing function. +****************************************************************************/ +static void process_browse_packet(struct packet_struct *p, char *buf,int len) +{ + struct dgram_packet *dgram = &p->packet.dgram; + int command = CVAL(buf,0); + struct subnet_record *subrec = find_subnet_for_dgram_browse_packet(p); + + /* Drop the packet if it's a different NetBIOS scope, or + the source is from one of our names. */ + + if (!strequal(dgram->dest_name.scope, lp_netbios_scope())) + { + DEBUG(7,("process_browse_packet: Discarding datagram from IP %s. Scope (%s) \ +mismatch with our scope (%s).\n", inet_ntoa(p->ip), dgram->dest_name.scope, lp_netbios_scope())); + return; + } + + if (is_myname(dgram->source_name.name)) + { + DEBUG(0,("process_browse_packet: Discarding datagram from IP %s. Source name \ +%s is one of our names !\n", inet_ntoa(p->ip), nmb_namestr(&dgram->source_name))); + return; + } + + switch (command) + { + case ANN_HostAnnouncement: + { + debug_browse_data(buf, len); + process_host_announce(subrec, p, buf+1); + break; + } + case ANN_DomainAnnouncement: + { + debug_browse_data(buf, len); + process_workgroup_announce(subrec, p, buf+1); + break; + } + case ANN_LocalMasterAnnouncement: + { + debug_browse_data(buf, len); + process_local_master_announce(subrec, p, buf+1); + break; + } + case ANN_AnnouncementRequest: + { + debug_browse_data(buf, len); + process_announce_request(subrec, p, buf+1); + break; + } + case ANN_Election: + { + debug_browse_data(buf, len); + process_election(subrec, p, buf+1); + break; + } + case ANN_GetBackupListReq: + { + debug_browse_data(buf, len); + process_get_backup_list_request(subrec, p, buf+1); + break; + } + case ANN_GetBackupListResp: + { + debug_browse_data(buf, len); + /* We never send ANN_GetBackupListReq so we + should never get these. */ + DEBUG(0,("process_browse_packet: Discarding GetBackupListResponse \ +packet from %s IP %s\n", nmb_namestr(&dgram->source_name), inet_ntoa(p->ip))); + break; + } + case ANN_ResetBrowserState: + { + debug_browse_data(buf, len); + process_reset_browser(subrec, p, buf+1); + break; + } + case ANN_MasterAnnouncement: + { + /* Master browser datagrams must be processed + on the unicast subnet. */ + subrec = unicast_subnet; + + debug_browse_data(buf, len); + process_master_browser_announce(subrec, p, buf+1); + break; + } + case ANN_BecomeBackup: + { + /* + * We don't currently implement this. Log it just in case. + */ + debug_browse_data(buf, len); + DEBUG(10,("process_browse_packet: On subnet %s ignoring browse packet \ +command ANN_BecomeBackup from %s IP %s to %s\n", + subrec->subnet_name, nmb_namestr(&dgram->source_name), + inet_ntoa(p->ip), nmb_namestr(&dgram->dest_name))); + break; + } + default: + { + debug_browse_data(buf, len); + DEBUG(0,("process_browse_packet: On subnet %s ignoring browse packet \ +command code %d from %s IP %s to %s\n", + subrec->subnet_name, command, nmb_namestr(&dgram->source_name), + inet_ntoa(p->ip), nmb_namestr(&dgram->dest_name))); + } + } +} + +/**************************************************************************** + Dispatch a LanMan browse frame from port 138 to the correct processing function. +****************************************************************************/ +static void process_lanman_packet(struct packet_struct *p, char *buf,int len) +{ + struct dgram_packet *dgram = &p->packet.dgram; + int command = SVAL(buf,0); + struct subnet_record *subrec = find_subnet_for_dgram_browse_packet(p); + + /* Drop the packet if it's a different NetBIOS scope, or + the source is from one of our names. */ + + if (!strequal(dgram->dest_name.scope, lp_netbios_scope())) + { + DEBUG(7,("process_lanman_packet: Discarding datagram from IP %s. Scope (%s) \ +mismatch with our scope (%s).\n", inet_ntoa(p->ip), dgram->dest_name.scope, lp_netbios_scope())); + return; + } + + if (is_myname(dgram->source_name.name)) + { + DEBUG(0,("process_lanman_packet: Discarding datagram from IP %s. Source name \ +%s is one of our names !\n", inet_ntoa(p->ip), nmb_namestr(&dgram->source_name))); + return; + } + + switch (command) + { + case ANN_HostAnnouncement: + { + debug_browse_data(buf, len); + process_lm_host_announce(subrec, p, buf+1); + break; + } + case ANN_AnnouncementRequest: + { + process_lm_announce_request(subrec, p, buf+1); + break; + } + default: + { + DEBUG(0,("process_lanman_packet: On subnet %s ignoring browse packet \ +command code %d from %s IP %s to %s\n", + subrec->subnet_name, command, nmb_namestr(&dgram->source_name), + inet_ntoa(p->ip), nmb_namestr(&dgram->dest_name))); + } + } +} + +/**************************************************************************** + Determine if a packet is for us on port 138. Note that to have any chance of + being efficient we need to drop as many packets as possible at this + stage as subsequent processing is expensive. +****************************************************************************/ + +static BOOL listening(struct packet_struct *p,struct nmb_name *nbname) +{ + struct subnet_record *subrec = NULL; + + for (subrec = FIRST_SUBNET; subrec ; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) + { + if(same_net(p->ip, subrec->bcast_ip, subrec->mask_ip)) + break; + } + + if(subrec == NULL) + subrec = unicast_subnet; + + return (find_name_on_subnet(subrec, nbname, FIND_SELF_NAME) != NULL); +} + +/**************************************************************************** + Process udp 138 datagrams +****************************************************************************/ +static void process_dgram(struct packet_struct *p) +{ + char *buf; + char *buf2; + int len; + struct dgram_packet *dgram = &p->packet.dgram; + + /* If we aren't listening to the destination name then ignore the packet */ + if (!listening(p,&dgram->dest_name)) + { + unexpected_packet(p); + DEBUG(5,("process_dgram: ignoring dgram packet sent to name %s from %s\n", + nmb_namestr(&dgram->dest_name), inet_ntoa(p->ip))); + return; + } + + if (dgram->header.msg_type != 0x10 && + dgram->header.msg_type != 0x11 && + dgram->header.msg_type != 0x12) + { + unexpected_packet(p); + /* Don't process error packets etc yet */ + DEBUG(5,("process_dgram: ignoring dgram packet sent to name %s from IP %s as it is \ +an error packet of type %x\n", + nmb_namestr(&dgram->dest_name), inet_ntoa(p->ip), dgram->header.msg_type)); + return; + } + + buf = &dgram->data[0]; + buf -= 4; /* XXXX for the pseudo tcp length - + someday I need to get rid of this */ + + if (CVAL(buf,smb_com) != SMBtrans) + return; + + len = SVAL(buf,smb_vwv11); + buf2 = smb_base(buf) + SVAL(buf,smb_vwv12); + + if (len <= 0) + return; + + if (buf2 + len > buf + sizeof(dgram->data)) { + DEBUG(2,("process_dgram: datagram from %s to %s IP %s for %s len=%d too long.\n", + nmb_namestr(&dgram->source_name),nmb_namestr(&dgram->dest_name), + inet_ntoa(p->ip), smb_buf(buf),len)); + len = (buf + sizeof(dgram->data)) - buf; + } + + DEBUG(4,("process_dgram: datagram from %s to %s IP %s for %s of type %d len=%d\n", + nmb_namestr(&dgram->source_name),nmb_namestr(&dgram->dest_name), + inet_ntoa(p->ip), smb_buf(buf),CVAL(buf2,0),len)); + + + /* Datagram packet received for the browser mailslot */ + if (strequal(smb_buf(buf),BROWSE_MAILSLOT)) + { + process_browse_packet(p,buf2,len); + return; + } + + /* Datagram packet received for the LAN Manager mailslot */ + if (strequal(smb_buf(buf),LANMAN_MAILSLOT)) { + process_lanman_packet(p,buf2,len); + return; + } + + /* Datagram packet received for the domain logon mailslot */ + if (strequal(smb_buf(buf),NET_LOGON_MAILSLOT)) + { + process_logon_packet(p,buf2,len,NET_LOGON_MAILSLOT); + return; + } + + /* Datagram packet received for the NT domain logon mailslot */ + if (strequal(smb_buf(buf),NT_LOGON_MAILSLOT)) + { + process_logon_packet(p,buf2,len,NT_LOGON_MAILSLOT); + return; + } + + unexpected_packet(p); +} + +/**************************************************************************** + Validate a response nmb packet. +****************************************************************************/ + +static BOOL validate_nmb_response_packet( struct nmb_packet *nmb ) +{ + BOOL ignore = False; + + switch (nmb->header.opcode) + { + case NMB_NAME_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.ancount == 0) + { + DEBUG(0,("validate_nmb_response_packet: Bad REG/REFRESH Packet. ")); + ignore = True; + } + break; + + case NMB_NAME_QUERY_OPCODE: + if ((nmb->header.ancount != 0) && (nmb->header.ancount != 1)) + { + DEBUG(0,("validate_nmb_response_packet: Bad QUERY Packet. ")); + ignore = True; + } + break; + case NMB_NAME_RELEASE_OPCODE: + if (nmb->header.ancount == 0) + { + DEBUG(0,("validate_nmb_response_packet: Bad RELEASE Packet. ")); + ignore = True; + } + break; + case NMB_WACK_OPCODE: + /* Check WACK response here. */ + if (nmb->header.ancount != 1) + { + DEBUG(0,("validate_nmb_response_packet: Bad WACK Packet. ")); + ignore = True; + } + break; + default: + DEBUG(0,("validate_nmb_response_packet: Ignoring packet with unknown opcode %d.\n", + nmb->header.opcode)); + return True; + } + + if(ignore) + DEBUG(0,("Ignoring response packet with opcode %d.\n", nmb->header.opcode)); + + return ignore; +} + +/**************************************************************************** + Validate a request nmb packet. +****************************************************************************/ + +static BOOL validate_nmb_packet( struct nmb_packet *nmb ) +{ + BOOL ignore = False; + + switch (nmb->header.opcode) + { + case NMB_NAME_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. */ + case NMB_NAME_MULTIHOMED_REG_OPCODE: + if (nmb->header.qdcount==0 || nmb->header.arcount==0) + { + DEBUG(0,("validate_nmb_packet: Bad REG/REFRESH Packet. ")); + ignore = True; + } + break; + + case NMB_NAME_QUERY_OPCODE: + if ((nmb->header.qdcount == 0) || + ((nmb->question.question_type != QUESTION_TYPE_NB_QUERY) && + (nmb->question.question_type != QUESTION_TYPE_NB_STATUS))) + { + DEBUG(0,("validate_nmb_packet: Bad QUERY Packet. ")); + ignore = True; + } + break; + + case NMB_NAME_RELEASE_OPCODE: + if (nmb->header.qdcount==0 || nmb->header.arcount==0) + { + DEBUG(0,("validate_nmb_packet: Bad RELEASE Packet. ")); + ignore = True; + } + break; + default: + DEBUG(0,("validate_nmb_packet: Ignoring packet with unknown opcode %d.\n", + nmb->header.opcode)); + return True; + } + + if(ignore) + DEBUG(0,("validate_nmb_packet: Ignoring request packet with opcode %d.\n", nmb->header.opcode)); + + return ignore; +} + +/**************************************************************************** + Find a subnet (and potentially a response record) for a packet. +****************************************************************************/ + +static struct subnet_record *find_subnet_for_nmb_packet( struct packet_struct *p, + struct response_record **pprrec) +{ + struct nmb_packet *nmb = &p->packet.nmb; + struct response_record *rrec = NULL; + struct subnet_record *subrec = NULL; + + if(pprrec != NULL) + *pprrec = NULL; + + if(nmb->header.response) + { + /* It's a response packet. Find a record for it or it's an error. */ + + rrec = find_response_record( &subrec, nmb->header.name_trn_id); + if(rrec == NULL) + { + DEBUG(3,("find_subnet_for_nmb_packet: response record not found for response id %hu\n", + nmb->header.name_trn_id)); + unexpected_packet(p); + return NULL; + } + + if(subrec == NULL) + { + DEBUG(0,("find_subnet_for_nmb_packet: subnet record not found for response id %hu\n", + nmb->header.name_trn_id)); + return NULL; + } + + if(pprrec != NULL) + *pprrec = rrec; + return subrec; + } + + /* Try and see what subnet this packet belongs to. */ + + /* WINS server ? */ + if(packet_is_for_wins_server(p)) + return wins_server_subnet; + + /* If it wasn't a broadcast packet then send to the UNICAST subnet. */ + if(nmb->header.nm_flags.bcast == False) + return unicast_subnet; + + /* Go through all the broadcast subnets and see if the mask matches. */ + for (subrec = FIRST_SUBNET; subrec ; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) + { + if(same_net(p->ip, subrec->bcast_ip, subrec->mask_ip)) + return subrec; + } + + /* If none match it must have been a directed broadcast - assign + the remote_broadcast_subnet. */ + return remote_broadcast_subnet; +} + +/**************************************************************************** + Process a nmb request packet - validate the packet and route it. +****************************************************************************/ + +static void process_nmb_request(struct packet_struct *p) +{ + struct nmb_packet *nmb = &p->packet.nmb; + struct subnet_record *subrec = NULL; + + debug_nmb_packet(p); + + /* Ensure we have a good packet. */ + if(validate_nmb_packet(nmb)) + return; + + /* Allocate a subnet to this packet - if we cannot - fail. */ + if((subrec = find_subnet_for_nmb_packet(p, NULL))==NULL) + return; + + switch (nmb->header.opcode) + { + case NMB_NAME_REG_OPCODE: + if(subrec == wins_server_subnet) + wins_process_name_registration_request(subrec, p); + else + process_name_registration_request(subrec, p); + break; + + case NMB_NAME_REFRESH_OPCODE_8: /* ambiguity in rfc1002 about which is correct. */ + case NMB_NAME_REFRESH_OPCODE_9: + if(subrec == wins_server_subnet) + wins_process_name_refresh_request(subrec, p); + else + process_name_refresh_request(subrec, p); + break; + + case NMB_NAME_MULTIHOMED_REG_OPCODE: + if(subrec == wins_server_subnet) + wins_process_multihomed_name_registration_request(subrec, p); + else + { + DEBUG(0,("process_nmb_request: Multihomed registration request must be \ +directed at a WINS server.\n")); + } + break; + + case NMB_NAME_QUERY_OPCODE: + switch (nmb->question.question_type) + { + case QUESTION_TYPE_NB_QUERY: + { + if(subrec == wins_server_subnet) + wins_process_name_query_request(subrec, p); + else + process_name_query_request(subrec, p); + break; + } + case QUESTION_TYPE_NB_STATUS: + { + if(subrec == wins_server_subnet) + { + DEBUG(0,("process_nmb_request: NB_STATUS request directed at WINS server is \ +not allowed.\n")); + break; + } + else + process_node_status_request(subrec, p); + break; + } + } + break; + + case NMB_NAME_RELEASE_OPCODE: + if(subrec == wins_server_subnet) + wins_process_name_release_request(subrec, p); + else + process_name_release_request(subrec, p); + break; + } +} + +/**************************************************************************** + Process a nmb response packet - validate the packet and route it. + to either the WINS server or a normal response. +****************************************************************************/ + +static void process_nmb_response(struct packet_struct *p) +{ + struct nmb_packet *nmb = &p->packet.nmb; + struct subnet_record *subrec = NULL; + struct response_record *rrec = NULL; + + debug_nmb_packet(p); + + if(validate_nmb_response_packet(nmb)) + return; + + if((subrec = find_subnet_for_nmb_packet(p, &rrec))==NULL) + return; + + if(rrec == NULL) + { + DEBUG(0,("process_nmb_response: response packet received but no response record \ +found for id = %hu. Ignoring packet.\n", nmb->header.name_trn_id)); + return; + } + + /* Increment the number of responses received for this record. */ + rrec->num_msgs++; + /* Ensure we don't re-send the request. */ + rrec->repeat_count = 0; + + /* Call the response received function for this packet. */ + (*rrec->resp_fn)(subrec, rrec, p); +} + + +/******************************************************************* + Run elements off the packet queue till its empty +******************************************************************/ + +void run_packet_queue(void) +{ + struct packet_struct *p; + + while ((p = packet_queue)) + { + packet_queue = p->next; + if (packet_queue) + packet_queue->prev = NULL; + p->next = p->prev = NULL; + + switch (p->packet_type) + { + case NMB_PACKET: + if(p->packet.nmb.header.response) + process_nmb_response(p); + else + process_nmb_request(p); + break; + + case DGRAM_PACKET: + process_dgram(p); + break; + } + free_packet(p); + } +} + +/******************************************************************* + Retransmit or timeout elements from all the outgoing subnet response + record queues. NOTE that this code must also check the WINS server + subnet for response records to timeout as the WINS server code + can send requests to check if a client still owns a name. + (Patch from Andrey Alekseyev <fetch@muffin.arcadia.spb.ru>). +******************************************************************/ + +void retransmit_or_expire_response_records(time_t t) +{ + struct subnet_record *subrec; + + for (subrec = FIRST_SUBNET; subrec; + subrec = get_next_subnet_maybe_unicast_or_wins_server(subrec)) + { + struct response_record *rrec, *nextrrec; + + for (rrec = subrec->responselist; rrec; rrec = nextrrec) + { + nextrrec = rrec->next; + + if (rrec->repeat_time <= t) + { + if (rrec->repeat_count > 0) + { + /* Resend while we have a non-zero repeat_count. */ + if(!send_packet(rrec->packet)) + { + DEBUG(0,("retransmit_or_expire_response_records: Failed to resend packet id %hu \ +to IP %s on subnet %s\n", rrec->response_id, inet_ntoa(rrec->packet->ip), + subrec->subnet_name)); + } + rrec->repeat_time = t + rrec->repeat_interval; + rrec->repeat_count--; + } + else + { + DEBUG(4,("retransmit_or_expire_response_records: timeout for packet id %hu to IP %s \ +on subnet %s\n", rrec->response_id, inet_ntoa(rrec->packet->ip), + subrec->subnet_name)); + + /* + * Check the flag in this record to prevent recursion if we end + * up in this function again via the timeout function call. + */ + + if(!rrec->in_expiration_processing) + { + + /* + * Set the recursion protection flag in this record. + */ + + rrec->in_expiration_processing = True; + + /* Call the timeout function. This will deal with removing the + timed out packet. */ + if(rrec->timeout_fn) + (*rrec->timeout_fn)(subrec, rrec); + else + { + /* We must remove the record ourself if there is + no timeout function. */ + remove_response_record(subrec, rrec); + } + } /* !rrec->in_expitation_processing */ + } /* rrec->repeat_count > 0 */ + } /* rrec->repeat_time <= t */ + } /* end for rrec */ + } /* end for subnet */ +} + +/**************************************************************************** + Create an fd_set containing all the sockets in the subnet structures, + plus the broadcast sockets. +***************************************************************************/ + +static BOOL create_listen_fdset(fd_set **ppset, int **psock_array, int *listen_number) +{ + int *sock_array = NULL; + struct subnet_record *subrec = NULL; + int count = 0; + int num = 0; + fd_set *pset = (fd_set *)malloc(sizeof(fd_set)); + + if(pset == NULL) + { + DEBUG(0,("create_listen_fdset: malloc fail !\n")); + return True; + } + + /* Check that we can add all the fd's we need. */ + for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) + count++; + + if((count*2) + 2 > FD_SETSIZE) + { + DEBUG(0,("create_listen_fdset: Too many file descriptors needed (%d). We can \ +only use %d.\n", (count*2) + 2, FD_SETSIZE)); + return True; + } + + if((sock_array = (int *)malloc(((count*2) + 2)*sizeof(int))) == NULL) + { + DEBUG(0,("create_listen_fdset: malloc fail for socket array.\n")); + return True; + } + + FD_ZERO(pset); + + /* Add in the broadcast socket on 137. */ + FD_SET(ClientNMB,pset); + sock_array[num++] = ClientNMB; + + /* Add in the 137 sockets on all the interfaces. */ + for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) + { + FD_SET(subrec->nmb_sock,pset); + sock_array[num++] = subrec->nmb_sock; + } + + /* Add in the broadcast socket on 138. */ + FD_SET(ClientDGRAM,pset); + sock_array[num++] = ClientDGRAM; + + /* Add in the 138 sockets on all the interfaces. */ + for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) + { + FD_SET(subrec->dgram_sock,pset); + sock_array[num++] = subrec->dgram_sock; + } + + *listen_number = (count*2) + 2; + + SAFE_FREE(*ppset); + SAFE_FREE(*psock_array); + + *ppset = pset; + *psock_array = sock_array; + + return False; +} + +/**************************************************************************** + Listens for NMB or DGRAM packets, and queues them. + return True if the socket is dead +***************************************************************************/ + +BOOL listen_for_packets(BOOL run_election) +{ + static fd_set *listen_set = NULL; + static int listen_number = 0; + static int *sock_array = NULL; + int i; + + fd_set fds; + int selrtn; + struct timeval timeout; +#ifndef SYNC_DNS + int dns_fd; +#endif + + if(listen_set == NULL || rescan_listen_set) + { + if(create_listen_fdset(&listen_set, &sock_array, &listen_number)) + { + DEBUG(0,("listen_for_packets: Fatal error. unable to create listen set. Exiting.\n")); + return True; + } + rescan_listen_set = False; + } + + memcpy((char *)&fds, (char *)listen_set, sizeof(fd_set)); + +#ifndef SYNC_DNS + dns_fd = asyncdns_fd(); + if (dns_fd != -1) { + FD_SET(dns_fd, &fds); + } +#endif + + + /* + * During elections and when expecting a netbios response packet we + * need to send election packets at tighter intervals. + * Ideally it needs to be the interval (in ms) between time now and + * the time we are expecting the next netbios packet. + */ + + timeout.tv_sec = (run_election||num_response_packets) ? 1 : NMBD_SELECT_LOOP; + timeout.tv_usec = 0; + + /* Prepare for the select - allow certain signals. */ + + BlockSignals(False, SIGTERM); + + selrtn = sys_select(FD_SETSIZE,&fds,NULL,NULL,&timeout); + + /* We can only take signals when we are in the select - block them again here. */ + + BlockSignals(True, SIGTERM); + + if(selrtn == -1) { + return False; + } + +#ifndef SYNC_DNS + if (dns_fd != -1 && FD_ISSET(dns_fd,&fds)) { + run_dns_queue(); + } +#endif + + for(i = 0; i < listen_number; i++) { + if (i < (listen_number/2)) { + /* Processing a 137 socket. */ + if (FD_ISSET(sock_array[i],&fds)) { + struct packet_struct *packet = read_packet(sock_array[i], NMB_PACKET); + if (packet) { + /* + * If we got a packet on the broadcast socket and interfaces + * only is set then check it came from one of our local nets. + */ + if(lp_bind_interfaces_only() && (sock_array[i] == ClientNMB) && + (!is_local_net(packet->ip))) { + DEBUG(7,("discarding nmb packet sent to broadcast socket from %s:%d\n", + inet_ntoa(packet->ip),packet->port)); + free_packet(packet); + } else if ((ip_equal(loopback_ip, packet->ip) || + ismyip(packet->ip)) && packet->port == global_nmb_port && + packet->packet.nmb.header.nm_flags.bcast) { + DEBUG(7,("discarding own bcast packet from %s:%d\n", + inet_ntoa(packet->ip),packet->port)); + free_packet(packet); + } else { + /* Save the file descriptor this packet came in on. */ + packet->fd = sock_array[i]; + queue_packet(packet); + } + } + } + } else { + /* Processing a 138 socket. */ + if (FD_ISSET(sock_array[i],&fds)) { + struct packet_struct *packet = read_packet(sock_array[i], DGRAM_PACKET); + if (packet) { + /* + * If we got a packet on the broadcast socket and interfaces + * only is set then check it came from one of our local nets. + */ + if(lp_bind_interfaces_only() && (sock_array[i] == ClientDGRAM) && + (!is_local_net(packet->ip))) { + DEBUG(7,("discarding dgram packet sent to broadcast socket from %s:%d\n", + inet_ntoa(packet->ip),packet->port)); + free_packet(packet); + } else if ((ip_equal(loopback_ip, packet->ip) || + ismyip(packet->ip)) && packet->port == DGRAM_PORT) { + DEBUG(7,("discarding own dgram packet from %s:%d\n", + inet_ntoa(packet->ip),packet->port)); + free_packet(packet); + } else { + /* Save the file descriptor this packet came in on. */ + packet->fd = sock_array[i]; + queue_packet(packet); + } + } + } + } /* end processing 138 socket. */ + } /* end for */ + return False; +} + +/**************************************************************************** + Construct and send a netbios DGRAM. +**************************************************************************/ +BOOL send_mailslot(BOOL unique, const char *mailslot,char *buf,int len, + const char *srcname, int src_type, + const char *dstname, int dest_type, + struct in_addr dest_ip,struct in_addr src_ip, + int dest_port) +{ + BOOL loopback_this_packet = False; + struct packet_struct p; + struct dgram_packet *dgram = &p.packet.dgram; + char *ptr,*p2; + char tmp[4]; + + memset((char *)&p,'\0',sizeof(p)); + + if(ismyip(dest_ip) && (dest_port == DGRAM_PORT)) /* Only if to DGRAM_PORT */ + loopback_this_packet = True; + + /* generate_name_trn_id(); */ /* Not used, so gone, RJS */ + + /* DIRECT GROUP or UNIQUE datagram. */ + dgram->header.msg_type = unique ? 0x10 : 0x11; + dgram->header.flags.node_type = M_NODE; + dgram->header.flags.first = True; + dgram->header.flags.more = False; + dgram->header.dgm_id = generate_name_trn_id(); + dgram->header.source_ip = src_ip; + dgram->header.source_port = DGRAM_PORT; + dgram->header.dgm_length = 0; /* Let build_dgram() handle this. */ + dgram->header.packet_offset = 0; + + make_nmb_name(&dgram->source_name,srcname,src_type); + make_nmb_name(&dgram->dest_name,dstname,dest_type); + + ptr = &dgram->data[0]; + + /* Setup the smb part. */ + ptr -= 4; /* XXX Ugliness because of handling of tcp SMB length. */ + memcpy(tmp,ptr,4); + set_message(ptr,17,23 + len,True); + memcpy(ptr,tmp,4); + + SCVAL(ptr,smb_com,SMBtrans); + SSVAL(ptr,smb_vwv1,len); + SSVAL(ptr,smb_vwv11,len); + SSVAL(ptr,smb_vwv12,70 + strlen(mailslot)); + SSVAL(ptr,smb_vwv13,3); + SSVAL(ptr,smb_vwv14,1); + SSVAL(ptr,smb_vwv15,1); + SSVAL(ptr,smb_vwv16,2); + p2 = smb_buf(ptr); + pstrcpy(p2,mailslot); + p2 = skip_string(p2,1); + + memcpy(p2,buf,len); + p2 += len; + + dgram->datasize = PTR_DIFF(p2,ptr+4); /* +4 for tcp length. */ + + p.ip = dest_ip; + p.port = dest_port; + p.fd = find_subnet_mailslot_fd_for_address( src_ip ); + p.timestamp = time(NULL); + p.packet_type = DGRAM_PACKET; + + DEBUG(4,("send_mailslot: Sending to mailslot %s from %s IP %s ", mailslot, + nmb_namestr(&dgram->source_name), inet_ntoa(src_ip))); + DEBUG(4,("to %s IP %s\n", nmb_namestr(&dgram->dest_name), inet_ntoa(dest_ip))); + + debug_browse_data(buf, len); + + if(loopback_this_packet) + { + struct packet_struct *lo_packet = NULL; + DEBUG(5,("send_mailslot: sending packet to ourselves.\n")); + if((lo_packet = copy_packet(&p)) == NULL) + return False; + queue_packet(lo_packet); + return True; + } + else + return(send_packet(&p)); +} diff --git a/source4/nmbd/nmbd_processlogon.c b/source4/nmbd/nmbd_processlogon.c new file mode 100644 index 0000000000..1fcfd11a3e --- /dev/null +++ b/source4/nmbd/nmbd_processlogon.c @@ -0,0 +1,480 @@ +/* + Unix SMB/CIFS implementation. + NBT netbios routines and daemon - version 2 + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Luke Kenneth Casson Leighton 1994-1998 + Copyright (C) Jeremy Allison 1994-1998 + Copyright (C) Jim McDonough 2002 + Copyright (C) Anthony Liguori 2002 + + 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. + + Revision History: + +*/ + +#include "includes.h" + +struct sam_database_info { + uint32 index; + uint32 serial_lo, serial_hi; + uint32 date_lo, date_hi; +}; + +/**************************************************************************** +Send a message to smbd to do a sam delta sync +**************************************************************************/ +static void send_repl_message(uint32 low_serial) +{ + TDB_CONTEXT *tdb; + + tdb = tdb_open_log(lock_path("connections.tdb"), 0, + TDB_DEFAULT, O_RDONLY, 0); + + if (!tdb) { + DEBUG(3, ("send_repl_message(): failed to open connections " + "database\n")); + return; + } + + DEBUG(3, ("sending replication message, serial = 0x%04x\n", + low_serial)); + + message_send_all(tdb, MSG_SMB_SAM_REPL, &low_serial, + sizeof(low_serial), False, NULL); + + tdb_close(tdb); +} + +/**************************************************************************** +Process a domain logon packet +**************************************************************************/ + +void process_logon_packet(struct packet_struct *p, char *buf,int len, + const char *mailslot) +{ + struct dgram_packet *dgram = &p->packet.dgram; + pstring my_name; + fstring reply_name; + pstring outbuf; + int code; + uint16 token = 0; + uint32 ntversion = 0; + uint16 lmnttoken = 0; + uint16 lm20token = 0; + uint32 domainsidsize; + BOOL short_request = False; + char *getdc; + char *uniuser; /* Unicode user name. */ + pstring ascuser; + char *unicomp; /* Unicode computer name. */ + + memset(outbuf, 0, sizeof(outbuf)); + + if (!lp_domain_logons()) + { + DEBUG(3,("process_logon_packet: Logon packet received from IP %s and domain \ +logons are not enabled.\n", inet_ntoa(p->ip) )); + return; + } + + pstrcpy(my_name, lp_netbios_name()); + + code = SVAL(buf,0); + DEBUG(1,("process_logon_packet: Logon from %s: code = 0x%x\n", inet_ntoa(p->ip), code)); + + switch (code) + { + case 0: + { + char *q = buf + 2; + char *machine = q; + char *user = skip_string(machine,1); + + getdc = skip_string(user,1); + q = skip_string(getdc,1); + token = SVAL(q,3); + + fstrcpy(reply_name,my_name); + + DEBUG(3,("process_logon_packet: Domain login request from %s at IP %s user=%s token=%x\n", + machine,inet_ntoa(p->ip),user,token)); + + q = outbuf; + SSVAL(q, 0, 6); + q += 2; + + fstrcpy(reply_name, "\\\\"); + fstrcat(reply_name, my_name); + fstrcpy(q, reply_name); q = skip_string(q, 1); /* PDC name */ + + SSVAL(q, 0, token); + q += 2; + + dump_data(4, outbuf, PTR_DIFF(q, outbuf)); + + send_mailslot(True, getdc, + outbuf,PTR_DIFF(q,outbuf), + lp_netbios_name(), 0x0, + machine, + dgram->source_name.name_type, + p->ip, *iface_ip(p->ip), p->port); + break; + } + + case QUERYFORPDC: + { + char *q = buf + 2; + char *machine = q; + + if (!lp_domain_master()) + { + /* We're not Primary Domain Controller -- ignore this */ + return; + } + + getdc = skip_string(machine,1); + q = skip_string(getdc,1); + q = ALIGN2(q, buf); + + /* at this point we can work out if this is a W9X or NT style + request. Experiments show that the difference is wether the + packet ends here. For a W9X request we now end with a pair of + bytes (usually 0xFE 0xFF) whereas with NT we have two further + strings - the following is a simple way of detecting this */ + if (len - PTR_DIFF(q, buf) <= 3) { + short_request = True; + } else { + unicomp = q; + + /* A full length (NT style) request */ + q = skip_unibuf(unicomp, PTR_DIFF(buf + len, unicomp)); + + if (len - PTR_DIFF(q, buf) > 8) { + /* with NT5 clients we can sometimes + get additional data - a length specificed string + containing the domain name, then 16 bytes of + data (no idea what it is) */ + int dom_len = CVAL(q, 0); + q++; + if (dom_len != 0) { + q += dom_len + 1; + } + q += 16; + } + ntversion = IVAL(q, 0); + lmnttoken = SVAL(q, 4); + lm20token = SVAL(q, 6); + } + + /* Construct reply. */ + q = outbuf; + SSVAL(q, 0, QUERYFORPDC_R); + q += 2; + + fstrcpy(reply_name,my_name); + fstrcpy(q, reply_name); + q = skip_string(q, 1); /* PDC name */ + + /* PDC and domain name */ + if (!short_request) /* Make a full reply */ + { + q = ALIGN2(q, outbuf); + + q += dos_PutUniCode(q, my_name, sizeof(pstring), True); /* PDC name */ + q += dos_PutUniCode(q, lp_workgroup(),sizeof(pstring), True); /* Domain name*/ + SIVAL(q, 0, 1); /* our nt version */ + SSVAL(q, 4, 0xffff); /* our lmnttoken */ + SSVAL(q, 6, 0xffff); /* our lm20token */ + q += 8; + } + + /* RJS, 21-Feb-2000, we send a short reply if the request was short */ + + DEBUG(3,("process_logon_packet: GETDC request from %s at IP %s, \ +reporting %s domain %s 0x%x ntversion=%x lm_nt token=%x lm_20 token=%x\n", + machine,inet_ntoa(p->ip), reply_name, lp_workgroup(), + QUERYFORPDC_R, (uint32)ntversion, (uint32)lmnttoken, + (uint32)lm20token )); + + dump_data(4, outbuf, PTR_DIFF(q, outbuf)); + + send_mailslot(True, getdc, + outbuf,PTR_DIFF(q,outbuf), + lp_netbios_name(), 0x0, + dgram->source_name.name, + dgram->source_name.name_type, + p->ip, *iface_ip(p->ip), p->port); + return; + } + + case SAMLOGON: + { + char *q = buf + 2; + fstring asccomp; + + q += 2; + unicomp = q; + uniuser = skip_unibuf(unicomp, PTR_DIFF(buf+len, unicomp)); + getdc = skip_unibuf(uniuser,PTR_DIFF(buf+len, uniuser)); + q = skip_string(getdc,1); + q += 4; /* Account Control Bits - indicating username type */ + domainsidsize = IVAL(q, 0); + q += 4; + + DEBUG(3,("process_logon_packet: SAMLOGON sidsize %d, len = %d\n", domainsidsize, len)); + + if (domainsidsize < (len - PTR_DIFF(q, buf)) && (domainsidsize != 0)) { + q += domainsidsize; + q = ALIGN4(q, buf); + } + + DEBUG(3,("process_logon_packet: len = %d PTR_DIFF(q, buf) = %d\n", len, PTR_DIFF(q, buf) )); + + if (len - PTR_DIFF(q, buf) > 8) { + /* with NT5 clients we can sometimes + get additional data - a length specificed string + containing the domain name, then 16 bytes of + data (no idea what it is) */ + int dom_len = CVAL(q, 0); + q++; + if (dom_len < (len - PTR_DIFF(q, buf)) && (dom_len != 0)) { + q += dom_len + 1; + } + q += 16; + } + + ntversion = IVAL(q, 0); + lmnttoken = SVAL(q, 4); + lm20token = SVAL(q, 6); + q += 8; + + DEBUG(3,("process_logon_packet: SAMLOGON sidsize %d ntv %d\n", domainsidsize, ntversion)); + + /* + * we respond regadless of whether the machine is in our password + * database. If it isn't then we let smbd send an appropriate error. + * Let's ignore the SID. + */ + pull_ucs2_pstring(ascuser, uniuser); + pull_ucs2_fstring(asccomp, unicomp); + DEBUG(3,("process_logon_packet: SAMLOGON user %s\n", ascuser)); + + fstrcpy(reply_name, "\\\\"); /* Here it wants \\LOGONSERVER. */ + fstrcat(reply_name, my_name); + + DEBUG(3,("process_logon_packet: SAMLOGON request from %s(%s) for %s, returning logon svr %s domain %s code %x token=%x\n", + asccomp,inet_ntoa(p->ip), ascuser, reply_name, lp_workgroup(), + SAMLOGON_R ,lmnttoken)); + + /* Construct reply. */ + + q = outbuf; + /* we want the simple version unless we are an ADS PDC..which means */ + /* never, at least for now */ + if ((ntversion < 11) || (SEC_ADS != lp_security()) || (ROLE_DOMAIN_PDC != lp_server_role())) { + if (SVAL(uniuser, 0) == 0) { + SSVAL(q, 0, SAMLOGON_UNK_R); /* user unknown */ + } else { + SSVAL(q, 0, SAMLOGON_R); + } + + q += 2; + + q += dos_PutUniCode(q, reply_name,sizeof(pstring), True); + q += dos_PutUniCode(q, ascuser, sizeof(pstring), True); + q += dos_PutUniCode(q, lp_workgroup(),sizeof(pstring), True); + } +#ifdef HAVE_ADS + else { + GUID domain_guid; + pstring domain; + char *hostname = NULL; + char *component, *dc, *q1; + uint8 size; + + get_mydomname(domain); + hostname = get_myname(); + + if (SVAL(uniuser, 0) == 0) { + SSVAL(q, 0, SAMLOGON_AD_UNK_R); /* user unknown */ + } else { + SSVAL(q, 0, SAMLOGON_AD_R); + } + q += 2; + + SSVAL(q, 0, 0); + q += 2; + SIVAL(q, 0, ADS_PDC|ADS_GC|ADS_LDAP|ADS_DS| + ADS_KDC|ADS_TIMESERV|ADS_CLOSEST|ADS_WRITABLE); + q += 4; + + /* Push Domain GUID */ + if (False == secrets_fetch_domain_guid(domain, &domain_guid)) { + DEBUG(2, ("Could not fetch DomainGUID for %s\n", domain)); + return; + } + memcpy(q, &domain_guid, sizeof(domain_guid)); + q += sizeof(domain_guid); + + /* Push domain components */ + dc = domain; + q1 = q; + while ((component = strtok(dc, "."))) { + dc = NULL; + size = push_ascii(&q[1], component, -1, 0); + SCVAL(q, 0, size); + q += (size + 1); + } + SCVAL(q, 0, 0); q++; + SSVAL(q, 0, 0x18c0); /* not sure what this is for, but */ + q += 2; /* it must follow the domain name. */ + + /* Push dns host name */ + size = push_ascii(&q[1], hostname, -1, 0); + SCVAL(q, 0, size); + q += (size + 1); + SSVAL(q, 0, 0x18c0); /* not sure what this is for, but */ + q += 2; /* it must follow the domain name. */ + + /* Push NETBIOS of domain */ + size = push_ascii(&q[1], lp_workgroup(), -1, STR_UPPER); + SCVAL(q, 0, size); + q += (size + 1); + SCVAL(q, 0, 0); q++; /* is this a null terminator or empty field */ + /* null terminator would not be needed because size is included */ + + /* Push NETBIOS of hostname */ + size = push_ascii(&q[1], my_name, -1, 0); + SCVAL(q, 0, size); + q += (size + 1); + SCVAL(q, 0, 0); q++; /* null terminator or empty field? */ + + /* Push user account */ + size = push_ascii(&q[1], ascuser, -1, 0); + SCVAL(q, 0, size); + q += (size + 1); + + /* Push 'Default-First-Site-Name' */ + size = push_ascii(&q[1], "Default-First-Site-Name", -1, 0); + SCVAL(q, 0, size); + q += (size + 1); + + SSVAL(q, 0, 0xc000); /* unknown */ + SCVAL(q, 2, PTR_DIFF(q,q1)); + SCVAL(q, 3, 0x10); /* unknown */ + q += 4; + + SIVAL(q, 0, 0x00000002); q += 4; /* unknown */ + SIVAL(q, 0, (iface_ip(p->ip))->s_addr); q += 4; + SIVAL(q, 0, 0x00000000); q += 4; /* unknown */ + SIVAL(q, 0, 0x00000000); q += 4; /* unknown */ + if (hostname) free(hostname); + } +#endif + + /* tell the client what version we are */ + SIVAL(q, 0, ((ntversion < 11) || (SEC_ADS != lp_security())) ? 1 : 13); + /* our ntversion */ + SSVAL(q, 4, 0xffff); /* our lmnttoken */ + SSVAL(q, 6, 0xffff); /* our lm20token */ + q += 8; + + dump_data(4, outbuf, PTR_DIFF(q, outbuf)); + + send_mailslot(True, getdc, + outbuf,PTR_DIFF(q,outbuf), + lp_netbios_name(), 0x0, + dgram->source_name.name, + dgram->source_name.name_type, + p->ip, *iface_ip(p->ip), p->port); + break; + } + + /* Announce change to UAS or SAM. Send by the domain controller when a + replication event is required. */ + + case SAM_UAS_CHANGE: { + struct sam_database_info *db_info; + char *q = buf + 2; + int i, db_count; + uint32 low_serial; + + /* Header */ + + low_serial = IVAL(q, 0); q += 4; /* Low serial number */ + + q += 4; /* Date/time */ + q += 4; /* Pulse */ + q += 4; /* Random */ + + /* Domain info */ + + q = skip_string(q, 1); /* PDC name */ + q = skip_string(q, 1); /* Domain name */ + q = skip_unibuf(q, PTR_DIFF(buf + len, q)); /* Unicode PDC name */ + q = skip_unibuf(q, PTR_DIFF(buf + len, q)); /* Unicode domain name */ + + /* Database info */ + + db_count = SVAL(q, 0); q += 2; + + db_info = (struct sam_database_info *) + malloc(sizeof(struct sam_database_info) * db_count); + + if (db_info == NULL) { + DEBUG(3, ("out of memory allocating info for %d databases\n", + db_count)); + return; + } + + for (i = 0; i < db_count; i++) { + db_info[i].index = IVAL(q, 0); + db_info[i].serial_lo = IVAL(q, 4); + db_info[i].serial_hi = IVAL(q, 8); + db_info[i].date_lo = IVAL(q, 12); + db_info[i].date_hi = IVAL(q, 16); + q += 20; + } + + /* Domain SID */ + + q += IVAL(q, 0) + 4; /* 4 byte length plus data */ + + q += 2; /* Alignment? */ + + /* Misc other info */ + + q += 4; /* NT version (0x1) */ + q += 2; /* LMNT token (0xff) */ + q += 2; /* LM20 token (0xff) */ + + SAFE_FREE(db_info); /* Not sure whether we need to do anything + useful with these */ + + /* Send message to smbd */ + + send_repl_message(low_serial); + + break; + } + + default: + { + DEBUG(3,("process_logon_packet: Unknown domain request %d\n",code)); + return; + } + } +} diff --git a/source4/nmbd/nmbd_responserecordsdb.c b/source4/nmbd/nmbd_responserecordsdb.c new file mode 100644 index 0000000000..7e8c8025ae --- /dev/null +++ b/source4/nmbd/nmbd_responserecordsdb.c @@ -0,0 +1,264 @@ +/* + Unix SMB/CIFS implementation. + NBT netbios library routines + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Luke Kenneth Casson Leighton 1994-1998 + Copyright (C) Jeremy Allison 1994-1998 + + 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" + +extern int ClientNMB; + +int num_response_packets = 0; + +/*************************************************************************** + Add an expected response record into the list + **************************************************************************/ + +static void add_response_record(struct subnet_record *subrec, + struct response_record *rrec) +{ + struct response_record *rrec2; + + num_response_packets++; /* count of total number of packets still around */ + + DEBUG(4,("add_response_record: adding response record id:%hu to subnet %s. num_records:%d\n", + rrec->response_id, subrec->subnet_name, num_response_packets)); + + if (!subrec->responselist) + { + subrec->responselist = rrec; + rrec->prev = NULL; + rrec->next = NULL; + return; + } + + for (rrec2 = subrec->responselist; rrec2->next; rrec2 = rrec2->next) + ; + + rrec2->next = rrec; + rrec->next = NULL; + rrec->prev = rrec2; +} + +/*************************************************************************** + Remove an expected response record from the list + **************************************************************************/ + +void remove_response_record(struct subnet_record *subrec, + struct response_record *rrec) +{ + if (rrec->prev) + rrec->prev->next = rrec->next; + if (rrec->next) + rrec->next->prev = rrec->prev; + + if (subrec->responselist == rrec) + subrec->responselist = rrec->next; + + if(rrec->userdata) + { + if(rrec->userdata->free_fn) { + (*rrec->userdata->free_fn)(rrec->userdata); + } else { + ZERO_STRUCTP(rrec->userdata); + SAFE_FREE(rrec->userdata); + } + } + + /* Ensure we can delete. */ + rrec->packet->locked = False; + free_packet(rrec->packet); + + ZERO_STRUCTP(rrec); + SAFE_FREE(rrec); + + num_response_packets--; /* count of total number of packets still around */ +} + +/**************************************************************************** + Create a response record for an outgoing packet. + **************************************************************************/ + +struct response_record *make_response_record( struct subnet_record *subrec, + struct packet_struct *p, + response_function resp_fn, + timeout_response_function timeout_fn, + success_function success_fn, + fail_function fail_fn, + struct userdata_struct *userdata) +{ + struct response_record *rrec; + struct nmb_packet *nmb = &p->packet.nmb; + + if (!(rrec = (struct response_record *)malloc(sizeof(*rrec)))) + { + DEBUG(0,("make_response_queue_record: malloc fail for response_record.\n")); + return NULL; + } + + memset((char *)rrec, '\0', sizeof(*rrec)); + + rrec->response_id = nmb->header.name_trn_id; + + rrec->resp_fn = resp_fn; + rrec->timeout_fn = timeout_fn; + rrec->success_fn = success_fn; + rrec->fail_fn = fail_fn; + + rrec->packet = p; + + if(userdata) + { + /* Intelligent userdata. */ + if(userdata->copy_fn) + { + if((rrec->userdata = (*userdata->copy_fn)(userdata)) == NULL) + { + DEBUG(0,("make_response_queue_record: copy fail for userdata.\n")); + ZERO_STRUCTP(rrec); + SAFE_FREE(rrec); + return NULL; + } + } + else + { + /* Primitive userdata, do a memcpy. */ + if((rrec->userdata = (struct userdata_struct *) + malloc(sizeof(struct userdata_struct)+userdata->userdata_len)) == NULL) + { + DEBUG(0,("make_response_queue_record: malloc fail for userdata.\n")); + ZERO_STRUCTP(rrec); + SAFE_FREE(rrec); + return NULL; + } + rrec->userdata->copy_fn = userdata->copy_fn; + rrec->userdata->free_fn = userdata->free_fn; + rrec->userdata->userdata_len = userdata->userdata_len; + memcpy(rrec->userdata->data, userdata->data, userdata->userdata_len); + } + } + else + rrec->userdata = NULL; + + rrec->num_msgs = 0; + + if(!nmb->header.nm_flags.bcast) + rrec->repeat_interval = 5; /* 5 seconds for unicast packets. */ + else + rrec->repeat_interval = 1; /* XXXX should be in ms */ + rrec->repeat_count = 3; /* 3 retries */ + rrec->repeat_time = time(NULL) + rrec->repeat_interval; /* initial retry time */ + + /* This packet is not being processed. */ + rrec->in_expiration_processing = False; + + /* Lock the packet so we won't lose it while it's on the list. */ + p->locked = True; + + add_response_record(subrec, rrec); + + return rrec; +} + +/**************************************************************************** + Find a response in a subnet's name query response list. + **************************************************************************/ + +static struct response_record *find_response_record_on_subnet( + struct subnet_record *subrec, uint16 id) +{ + struct response_record *rrec = NULL; + + for (rrec = subrec->responselist; rrec; rrec = rrec->next) + { + if (rrec->response_id == id) + { + DEBUG(4, ("find_response_record: found response record id = %hu on subnet %s\n", + id, subrec->subnet_name)); + break; + } + } + return rrec; +} + +/**************************************************************************** + Find a response in any subnet's name query response list. + **************************************************************************/ + +struct response_record *find_response_record(struct subnet_record **ppsubrec, + uint16 id) +{ + struct response_record *rrec = NULL; + + for ((*ppsubrec) = FIRST_SUBNET; (*ppsubrec); + (*ppsubrec) = NEXT_SUBNET_INCLUDING_UNICAST(*ppsubrec)) + { + if((rrec = find_response_record_on_subnet(*ppsubrec, id)) != NULL) + return rrec; + } + + /* There should never be response records on the remote_broadcast subnet. + Sanity check to ensure this is so. */ + if(remote_broadcast_subnet->responselist != NULL) + { + DEBUG(0,("find_response_record: response record found on subnet %s. This should \ +never happen !\n", remote_broadcast_subnet->subnet_name)); + } + + /* Now check the WINS server subnet if it exists. */ + if(wins_server_subnet != NULL) + { + *ppsubrec = wins_server_subnet; + if((rrec = find_response_record_on_subnet(*ppsubrec, id))!= NULL) + return rrec; + } + + DEBUG(0,("find_response_record: response packet id %hu received with no \ +matching record.\n", id)); + + *ppsubrec = NULL; + + return NULL; +} + +/**************************************************************************** + Check if a refresh is queued for a particular name on a particular subnet. + **************************************************************************/ + +BOOL is_refresh_already_queued(struct subnet_record *subrec, struct name_record *namerec) +{ + struct response_record *rrec = NULL; + + for (rrec = subrec->responselist; rrec; rrec = rrec->next) + { + struct packet_struct *p = rrec->packet; + struct nmb_packet *nmb = &p->packet.nmb; + + if((nmb->header.opcode == NMB_NAME_REFRESH_OPCODE_8) || + (nmb->header.opcode == NMB_NAME_REFRESH_OPCODE_9)) + { + /* Yes it's a queued refresh - check if the name is correct. */ + if(nmb_name_equal(&nmb->question.question_name, &namerec->name)) + return True; + } + } + + return False; +} diff --git a/source4/nmbd/nmbd_sendannounce.c b/source4/nmbd/nmbd_sendannounce.c new file mode 100644 index 0000000000..191a3b7c7b --- /dev/null +++ b/source4/nmbd/nmbd_sendannounce.c @@ -0,0 +1,607 @@ +/* + Unix SMB/CIFS implementation. + NBT netbios routines and daemon - version 2 + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Luke Kenneth Casson Leighton 1994-1998 + Copyright (C) Jeremy Allison 1994-1998 + + SMB Version handling + Copyright (C) John H Terpstra 1995-1998 + + 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" + +extern int updatecount; +extern BOOL found_lm_clients; + +/**************************************************************************** + Send a browser reset packet. +**************************************************************************/ + +void send_browser_reset(int reset_type, const char *to_name, int to_type, struct in_addr to_ip) +{ + pstring outbuf; + char *p; + + DEBUG(3,("send_browser_reset: sending reset request type %d to %s<%02x> IP %s.\n", + reset_type, to_name, to_type, inet_ntoa(to_ip) )); + + memset(outbuf,'\0',sizeof(outbuf)); + p = outbuf; + SCVAL(p,0,ANN_ResetBrowserState); + p++; + SCVAL(p,0,reset_type); + p++; + + send_mailslot(True, BROWSE_MAILSLOT, outbuf,PTR_DIFF(p,outbuf), + lp_netbios_name(), 0x0, to_name, to_type, to_ip, + FIRST_SUBNET->myip, DGRAM_PORT); +} + +/**************************************************************************** + Broadcast a packet to the local net requesting that all servers in this + workgroup announce themselves to us. + **************************************************************************/ + +void broadcast_announce_request(struct subnet_record *subrec, struct work_record *work) +{ + pstring outbuf; + char *p; + + work->needannounce = True; + + DEBUG(3,("broadcast_announce_request: sending announce request for workgroup %s \ +to subnet %s\n", work->work_group, subrec->subnet_name)); + + memset(outbuf,'\0',sizeof(outbuf)); + p = outbuf; + SCVAL(p,0,ANN_AnnouncementRequest); + p++; + + SCVAL(p,0,work->token); /* (local) Unique workgroup token id. */ + p++; + p += push_string(NULL, p+1, lp_netbios_name(), 15, STR_ASCII|STR_UPPER|STR_TERMINATE); + + send_mailslot(False, BROWSE_MAILSLOT, outbuf,PTR_DIFF(p,outbuf), + lp_netbios_name(), 0x0, work->work_group,0x1e, subrec->bcast_ip, + subrec->myip, DGRAM_PORT); +} + +/**************************************************************************** + Broadcast an announcement. + **************************************************************************/ + +static void send_announcement(struct subnet_record *subrec, int announce_type, + const char *from_name, const char *to_name, int to_type, struct in_addr to_ip, + time_t announce_interval, + const char *server_name, int server_type, const char *server_comment) +{ + pstring outbuf; + char *p; + + memset(outbuf,'\0',sizeof(outbuf)); + p = outbuf+1; + + SCVAL(outbuf,0,announce_type); + + /* Announcement parameters. */ + SCVAL(p,0,updatecount); + SIVAL(p,1,announce_interval*1000); /* Milliseconds - despite the spec. */ + + push_string(NULL, p+5, server_name, 15, STR_ASCII|STR_UPPER|STR_TERMINATE); + + SCVAL(p,21,lp_major_announce_version()); /* Major version. */ + SCVAL(p,22,lp_minor_announce_version()); /* Minor version. */ + + SIVAL(p,23,server_type & ~SV_TYPE_LOCAL_LIST_ONLY); + /* Browse version: got from NT/AS 4.00 - Value defined in smb.h (JHT). */ + SSVAL(p,27,BROWSER_ELECTION_VERSION); + SSVAL(p,29,BROWSER_CONSTANT); /* Browse signature. */ + + p += 31 + push_string(NULL, p+31, server_comment, -1, STR_ASCII|STR_TERMINATE); + + send_mailslot(False,BROWSE_MAILSLOT, outbuf, PTR_DIFF(p,outbuf), + from_name, 0x0, to_name, to_type, to_ip, subrec->myip, + DGRAM_PORT); +} + +/**************************************************************************** + Broadcast a LanMan announcement. +**************************************************************************/ + +static void send_lm_announcement(struct subnet_record *subrec, int announce_type, + char *from_name, char *to_name, int to_type, struct in_addr to_ip, + time_t announce_interval, + char *server_name, int server_type, char *server_comment) +{ + pstring outbuf; + char *p=outbuf; + + memset(outbuf,'\0',sizeof(outbuf)); + + SSVAL(p,0,announce_type); + SIVAL(p,2,server_type & ~SV_TYPE_LOCAL_LIST_ONLY); + SCVAL(p,6,lp_major_announce_version()); /* Major version. */ + SCVAL(p,7,lp_minor_announce_version()); /* Minor version. */ + SSVAL(p,8,announce_interval); /* In seconds - according to spec. */ + + p += 10; + /*StrnCpy(p,server_name,15); + strupper(p); + p = skip_string(p,1); + pstrcpy(p,server_comment); + p = skip_string(p,1);*/ + p += push_string(NULL, p, server_name, 15, STR_ASCII|STR_UPPER|STR_TERMINATE); + p += push_string(NULL, p, server_comment, sizeof(pstring)-15, STR_ASCII|STR_UPPER|STR_TERMINATE); + + send_mailslot(False,LANMAN_MAILSLOT, outbuf, PTR_DIFF(p,outbuf), + from_name, 0x0, to_name, to_type, to_ip, subrec->myip, + DGRAM_PORT); +} + +/**************************************************************************** + We are a local master browser. Announce this to WORKGROUP<1e>. +****************************************************************************/ + +static void send_local_master_announcement(struct subnet_record *subrec, struct work_record *work, + struct server_record *servrec) +{ + /* Ensure we don't have the prohibited bit set. */ + uint32 type = servrec->serv.type & ~SV_TYPE_LOCAL_LIST_ONLY; + + DEBUG(3,("send_local_master_announcement: type %x for name %s on subnet %s for workgroup %s\n", + type, lp_netbios_name(), subrec->subnet_name, work->work_group)); + + send_announcement(subrec, ANN_LocalMasterAnnouncement, + lp_netbios_name(), /* From nbt name. */ + work->work_group, 0x1e, /* To nbt name. */ + subrec->bcast_ip, /* To ip. */ + work->announce_interval, /* Time until next announce. */ + lp_netbios_name(), /* Name to announce. */ + type, /* Type field. */ + servrec->serv.comment); +} + +/**************************************************************************** + Announce the workgroup WORKGROUP to MSBROWSE<01>. +****************************************************************************/ + +static void send_workgroup_announcement(struct subnet_record *subrec, struct work_record *work) +{ + DEBUG(3,("send_workgroup_announcement: on subnet %s for workgroup %s\n", + subrec->subnet_name, work->work_group)); + + send_announcement(subrec, ANN_DomainAnnouncement, + lp_netbios_name(), /* From nbt name. */ + MSBROWSE, 0x1, /* To nbt name. */ + subrec->bcast_ip, /* To ip. */ + work->announce_interval, /* Time until next announce. */ + work->work_group, /* Name to announce. */ + SV_TYPE_DOMAIN_ENUM|SV_TYPE_NT, /* workgroup announce flags. */ + lp_netbios_name()); /* From name as comment. */ +} + +/**************************************************************************** + Announce the given host to WORKGROUP<1d>. +****************************************************************************/ + +static void send_host_announcement(struct subnet_record *subrec, struct work_record *work, + struct server_record *servrec) +{ + /* Ensure we don't have the prohibited bits set. */ + uint32 type = servrec->serv.type & ~SV_TYPE_LOCAL_LIST_ONLY; + + DEBUG(3,("send_host_announcement: type %x for host %s on subnet %s for workgroup %s\n", + type, servrec->serv.name, subrec->subnet_name, work->work_group)); + + send_announcement(subrec, ANN_HostAnnouncement, + servrec->serv.name, /* From nbt name. */ + work->work_group, 0x1d, /* To nbt name. */ + subrec->bcast_ip, /* To ip. */ + work->announce_interval, /* Time until next announce. */ + servrec->serv.name, /* Name to announce. */ + type, /* Type field. */ + servrec->serv.comment); +} + +/**************************************************************************** + Announce the given LanMan host +****************************************************************************/ + +static void send_lm_host_announcement(struct subnet_record *subrec, struct work_record *work, + struct server_record *servrec, int lm_interval) +{ + /* Ensure we don't have the prohibited bits set. */ + uint32 type = servrec->serv.type & ~SV_TYPE_LOCAL_LIST_ONLY; + + DEBUG(3,("send_lm_host_announcement: type %x for host %s on subnet %s for workgroup %s, ttl: %d\n", + type, servrec->serv.name, subrec->subnet_name, work->work_group, lm_interval)); + + send_lm_announcement(subrec, ANN_HostAnnouncement, + servrec->serv.name, /* From nbt name. */ + work->work_group, 0x00, /* To nbt name. */ + subrec->bcast_ip, /* To ip. */ + lm_interval, /* Time until next announce. */ + servrec->serv.name, /* Name to announce. */ + type, /* Type field. */ + servrec->serv.comment); +} + +/**************************************************************************** + Announce a server record. + ****************************************************************************/ + +static void announce_server(struct subnet_record *subrec, struct work_record *work, + struct server_record *servrec) +{ + /* Only do domain announcements if we are a master and it's + our primary name we're being asked to announce. */ + + if (AM_LOCAL_MASTER_BROWSER(work) && strequal(lp_netbios_name(),servrec->serv.name)) + { + send_local_master_announcement(subrec, work, servrec); + send_workgroup_announcement(subrec, work); + } + else + { + send_host_announcement(subrec, work, servrec); + } +} + +/**************************************************************************** + Go through all my registered names on all broadcast subnets and announce + them if the timeout requires it. + **************************************************************************/ + +void announce_my_server_names(time_t t) +{ + struct subnet_record *subrec; + + for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) + { + struct work_record *work = find_workgroup_on_subnet(subrec, lp_workgroup()); + + if(work) + { + struct server_record *servrec; + + if (work->needannounce) + { + /* Drop back to a max 3 minute announce. This is to prevent a + single lost packet from breaking things for too long. */ + + work->announce_interval = MIN(work->announce_interval, + CHECK_TIME_MIN_HOST_ANNCE*60); + work->lastannounce_time = t - (work->announce_interval+1); + work->needannounce = False; + } + + /* Announce every minute at first then progress to every 12 mins */ + if ((t - work->lastannounce_time) < work->announce_interval) + continue; + + if (work->announce_interval < (CHECK_TIME_MAX_HOST_ANNCE * 60)) + work->announce_interval += 60; + + work->lastannounce_time = t; + + for (servrec = work->serverlist; servrec; servrec = servrec->next) + { + if (is_myname(servrec->serv.name)) + announce_server(subrec, work, servrec); + } + } /* if work */ + } /* for subrec */ +} + +/**************************************************************************** + Go through all my registered names on all broadcast subnets and announce + them as a LanMan server if the timeout requires it. +**************************************************************************/ + +void announce_my_lm_server_names(time_t t) +{ + struct subnet_record *subrec; + static time_t last_lm_announce_time=0; + int announce_interval = lp_lm_interval(); + int lm_announce = lp_lm_announce(); + + if ((announce_interval <= 0) || (lm_announce <= 0)) + { + /* user absolutely does not want LM announcements to be sent. */ + return; + } + + if ((lm_announce >= 2) && (!found_lm_clients)) + { + /* has been set to 2 (Auto) but no LM clients detected (yet). */ + return; + } + + /* Otherwise: must have been set to 1 (Yes), or LM clients *have* + been detected. */ + + for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) + { + struct work_record *work = find_workgroup_on_subnet(subrec, lp_workgroup()); + + if(work) + { + struct server_record *servrec; + + if (last_lm_announce_time && ((t - last_lm_announce_time) < announce_interval )) + continue; + + last_lm_announce_time = t; + + for (servrec = work->serverlist; servrec; servrec = servrec->next) + { + if (is_myname(servrec->serv.name)) + /* skipping equivalent of announce_server() */ + send_lm_host_announcement(subrec, work, servrec, announce_interval); + } + } /* if work */ + } /* for subrec */ +} + +/* Announce timer. Moved into global static so it can be reset + when a machine becomes a local master browser. */ +static time_t announce_timer_last=0; + +/**************************************************************************** + Reset the announce_timer so that a local master browser announce will be done + immediately. + ****************************************************************************/ + +void reset_announce_timer(void) +{ + announce_timer_last = time(NULL) - (CHECK_TIME_MST_ANNOUNCE * 60); +} + +/**************************************************************************** + Announce myself as a local master browser to a domain master browser. + **************************************************************************/ + +void announce_myself_to_domain_master_browser(time_t t) +{ + struct subnet_record *subrec; + struct work_record *work; + + if(!we_are_a_wins_client()) + { + DEBUG(10,("announce_myself_to_domain_master_browser: no unicast subnet, ignoring.\n")); + return; + } + + if (!announce_timer_last) + announce_timer_last = t; + + if ((t-announce_timer_last) < (CHECK_TIME_MST_ANNOUNCE * 60)) + { + DEBUG(10,("announce_myself_to_domain_master_browser: t (%d) - last(%d) < %d\n", + (int)t, (int)announce_timer_last, + CHECK_TIME_MST_ANNOUNCE * 60 )); + return; + } + + announce_timer_last = t; + + /* Look over all our broadcast subnets to see if any of them + has the state set as local master browser. */ + + for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) + { + for (work = subrec->workgrouplist; work; work = work->next) + { + if (AM_LOCAL_MASTER_BROWSER(work)) + { + DEBUG(4,( "announce_myself_to_domain_master_browser: I am a local master browser for \ +workgroup %s on subnet %s\n", work->work_group, subrec->subnet_name)); + + /* Look in nmbd_browsersync.c for the rest of this code. */ + announce_and_sync_with_domain_master_browser(subrec, work); + } + } + } +} + +/**************************************************************************** +Announce all samba's server entries as 'gone'. +This must *only* be called on shutdown. +****************************************************************************/ + +void announce_my_servers_removed(void) +{ + int announce_interval = lp_lm_interval(); + int lm_announce = lp_lm_announce(); + struct subnet_record *subrec; + + for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) + { + struct work_record *work; + for (work = subrec->workgrouplist; work; work = work->next) + { + struct server_record *servrec; + + work->announce_interval = 0; + for (servrec = work->serverlist; servrec; servrec = servrec->next) + { + if (!is_myname(servrec->serv.name)) + continue; + servrec->serv.type = 0; + if(AM_LOCAL_MASTER_BROWSER(work)) + send_local_master_announcement(subrec, work, servrec); + send_host_announcement(subrec, work, servrec); + + + if ((announce_interval <= 0) || (lm_announce <= 0)) + { + /* user absolutely does not want LM announcements to be sent. */ + continue; + } + + if ((lm_announce >= 2) && (!found_lm_clients)) + { + /* has been set to 2 (Auto) but no LM clients detected (yet). */ + continue; + } + + /* + * lm announce was set or we have seen lm announcements, so do + * a lm announcement of host removed. + */ + + send_lm_host_announcement(subrec, work, servrec, 0); + } + } + } +} + +/**************************************************************************** + Do all the "remote" announcements. These are used to put ourselves + on a remote browse list. They are done blind, no checking is done to + see if there is actually a local master browser at the other end. + **************************************************************************/ + +void announce_remote(time_t t) +{ + char *s; + const char *ptr; + static time_t last_time = 0; + pstring s2; + struct in_addr addr; + char *comment; + int stype = lp_default_server_announce(); + + if (last_time && (t < (last_time + REMOTE_ANNOUNCE_INTERVAL))) + return; + + last_time = t; + + s = lp_remote_announce(); + if (!*s) + return; + + comment = string_truncate(lp_serverstring(), MAX_SERVER_STRING_LENGTH); + + for (ptr=s; next_token(&ptr,s2,NULL,sizeof(s2)); ) + { + /* The entries are of the form a.b.c.d/WORKGROUP with + WORKGROUP being optional */ + const char *wgroup; + char *pwgroup; + int i; + + pwgroup = strchr_m(s2,'/'); + if (pwgroup) + *pwgroup++ = 0; + if (!pwgroup || !*pwgroup) + wgroup = lp_workgroup(); + else + wgroup = pwgroup; + + addr = *interpret_addr2(s2); + + /* Announce all our names including aliases */ + /* Give the ip address as the address of our first + broadcast subnet. */ + + for(i=0; my_netbios_names(i); i++) + { + const char *name = my_netbios_names(i); + + DEBUG(5,("announce_remote: Doing remote announce for server %s to IP %s.\n", + name, inet_ntoa(addr) )); + + send_announcement(FIRST_SUBNET, ANN_HostAnnouncement, + name, /* From nbt name. */ + wgroup, 0x1d, /* To nbt name. */ + addr, /* To ip. */ + REMOTE_ANNOUNCE_INTERVAL, /* Time until next announce. */ + name, /* Name to announce. */ + stype, /* Type field. */ + comment); + } + } +} + +/**************************************************************************** + Implement the 'remote browse sync' feature Andrew added. + These are used to put our browse lists into remote browse lists. + **************************************************************************/ + +void browse_sync_remote(time_t t) +{ + char *s; + const char *ptr; + static time_t last_time = 0; + pstring s2; + struct in_addr addr; + struct work_record *work; + pstring outbuf; + char *p; + + if (last_time && (t < (last_time + REMOTE_ANNOUNCE_INTERVAL))) + return; + + last_time = t; + + s = lp_remote_browse_sync(); + if (!*s) + return; + + /* + * We only do this if we are the local master browser + * for our workgroup on the firsst subnet. + */ + + if((work = find_workgroup_on_subnet(FIRST_SUBNET, lp_workgroup())) == NULL) + { + DEBUG(0,("browse_sync_remote: Cannot find workgroup %s on subnet %s\n", + lp_workgroup(), FIRST_SUBNET->subnet_name )); + return; + } + + if(!AM_LOCAL_MASTER_BROWSER(work)) + { + DEBUG(5,("browse_sync_remote: We can only do this if we are a local master browser \ +for workgroup %s on subnet %s.\n", lp_workgroup(), FIRST_SUBNET->subnet_name )); + return; + } + + memset(outbuf,'\0',sizeof(outbuf)); + p = outbuf; + SCVAL(p,0,ANN_MasterAnnouncement); + p++; + + StrnCpy(p,lp_netbios_name(),15); + strupper(p); + p = skip_string(p,1); + + for (ptr=s; next_token(&ptr,s2,NULL,sizeof(s2)); ) + { + /* The entries are of the form a.b.c.d */ + addr = *interpret_addr2(s2); + + DEBUG(5,("announce_remote: Doing remote browse sync announce for server %s to IP %s.\n", + lp_netbios_name(), inet_ntoa(addr) )); + + send_mailslot(True, BROWSE_MAILSLOT, outbuf,PTR_DIFF(p,outbuf), + lp_netbios_name(), 0x0, "*", 0x0, addr, FIRST_SUBNET->myip, DGRAM_PORT); + } +} diff --git a/source4/nmbd/nmbd_serverlistdb.c b/source4/nmbd/nmbd_serverlistdb.c new file mode 100644 index 0000000000..ee0c021d5d --- /dev/null +++ b/source4/nmbd/nmbd_serverlistdb.c @@ -0,0 +1,448 @@ +/* + Unix SMB/CIFS implementation. + NBT netbios routines and daemon - version 2 + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Luke Kenneth Casson Leighton 1994-1998 + Copyright (C) Jeremy Allison 1994-1998 + + 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" + +extern int ClientNMB; + +int updatecount = 0; + +/******************************************************************* + Remove all the servers in a work group. + ******************************************************************/ + +void remove_all_servers(struct work_record *work) +{ + struct server_record *servrec; + struct server_record *nexts; + + for (servrec = work->serverlist; servrec; servrec = nexts) + { + DEBUG(7,("remove_all_servers: Removing server %s\n",servrec->serv.name)); + nexts = servrec->next; + + if (servrec->prev) + servrec->prev->next = servrec->next; + if (servrec->next) + servrec->next->prev = servrec->prev; + + if (work->serverlist == servrec) + work->serverlist = servrec->next; + + ZERO_STRUCTP(servrec); + SAFE_FREE(servrec); + + } + + work->subnet->work_changed = True; +} + +/*************************************************************************** + Add a server into the a workgroup serverlist. + **************************************************************************/ + +static void add_server_to_workgroup(struct work_record *work, + struct server_record *servrec) +{ + struct server_record *servrec2; + + if (!work->serverlist) + { + work->serverlist = servrec; + servrec->prev = NULL; + servrec->next = NULL; + return; + } + + for (servrec2 = work->serverlist; servrec2->next; servrec2 = servrec2->next) + ; + + servrec2->next = servrec; + servrec->next = NULL; + servrec->prev = servrec2; + work->subnet->work_changed = True; +} + +/**************************************************************************** + Find a server in a server list. + **************************************************************************/ + +struct server_record *find_server_in_workgroup(struct work_record *work, const char *name) +{ + struct server_record *ret; + + for (ret = work->serverlist; ret; ret = ret->next) + { + if (strequal(ret->serv.name,name)) + return ret; + } + return NULL; +} + + +/**************************************************************************** + Remove a server entry from this workgroup. + ****************************************************************************/ + +void remove_server_from_workgroup(struct work_record *work, struct server_record *servrec) +{ + if (servrec->prev) + servrec->prev->next = servrec->next; + if (servrec->next) + servrec->next->prev = servrec->prev; + + if (work->serverlist == servrec) + work->serverlist = servrec->next; + + ZERO_STRUCTP(servrec); + SAFE_FREE(servrec); + work->subnet->work_changed = True; +} + +/**************************************************************************** + Create a server entry on this workgroup. + ****************************************************************************/ + +struct server_record *create_server_on_workgroup(struct work_record *work, + const char *name,int servertype, + int ttl, const char *comment) +{ + struct server_record *servrec; + + if (name[0] == '*') + { + DEBUG(7,("create_server_on_workgroup: not adding name starting with '*' (%s)\n", + name)); + return (NULL); + } + + if((servrec = find_server_in_workgroup(work, name)) != NULL) + { + DEBUG(0,("create_server_on_workgroup: Server %s already exists on \ +workgroup %s. This is a bug.\n", name, work->work_group)); + return NULL; + } + + if((servrec = (struct server_record *)malloc(sizeof(*servrec))) == NULL) + { + DEBUG(0,("create_server_entry_on_workgroup: malloc fail !\n")); + return NULL; + } + + memset((char *)servrec,'\0',sizeof(*servrec)); + + servrec->subnet = work->subnet; + + StrnCpy(servrec->serv.name,name,sizeof(servrec->serv.name)-1); + StrnCpy(servrec->serv.comment,comment,sizeof(servrec->serv.comment)-1); + strupper(servrec->serv.name); + servrec->serv.type = servertype; + + update_server_ttl(servrec, ttl); + + add_server_to_workgroup(work, servrec); + + DEBUG(3,("create_server_on_workgroup: Created server entry %s of type %x (%s) on \ +workgroup %s.\n", name,servertype,comment, work->work_group)); + + work->subnet->work_changed = True; + + return(servrec); +} + +/******************************************************************* + Update the ttl field of a server record. +*******************************************************************/ + +void update_server_ttl(struct server_record *servrec, int ttl) +{ + if(ttl > lp_max_ttl()) + ttl = lp_max_ttl(); + + if(is_myname(servrec->serv.name)) + servrec->death_time = PERMANENT_TTL; + else + servrec->death_time = (ttl != PERMANENT_TTL) ? time(NULL)+(ttl*3) : PERMANENT_TTL; + + servrec->subnet->work_changed = True; +} + +/******************************************************************* + Expire old servers in the serverlist. A time of -1 indicates + everybody dies except those with a death_time of PERMANENT_TTL (which is 0). + This should only be called from expire_workgroups_and_servers(). + ******************************************************************/ + +void expire_servers(struct work_record *work, time_t t) +{ + struct server_record *servrec; + struct server_record *nexts; + + for (servrec = work->serverlist; servrec; servrec = nexts) + { + nexts = servrec->next; + + if ((servrec->death_time != PERMANENT_TTL) && ((t == -1) || (servrec->death_time < t))) + { + DEBUG(3,("expire_old_servers: Removing timed out server %s\n",servrec->serv.name)); + remove_server_from_workgroup(work, servrec); + work->subnet->work_changed = True; + } + } +} + +/******************************************************************* + Decide if we should write out a server record for this server. + We return zero if we should not. Check if we've already written + out this server record from an earlier subnet. +******************************************************************/ + +static uint32 write_this_server_name( struct subnet_record *subrec, + struct work_record *work, + struct server_record *servrec) +{ + struct subnet_record *ssub; + struct work_record *iwork; + + /* Go through all the subnets we have already seen. */ + for (ssub = FIRST_SUBNET; ssub != subrec; ssub = NEXT_SUBNET_INCLUDING_UNICAST(ssub)) + { + for(iwork = ssub->workgrouplist; iwork; iwork = iwork->next) + { + if(find_server_in_workgroup( iwork, servrec->serv.name) != NULL) + { + /* + * We have already written out this server record, don't + * do it again. This gives precedence to servers we have seen + * on the broadcast subnets over servers that may have been + * added via a sync on the unicast_subet. + * + * The correct way to do this is to have a serverlist file + * per subnet - this means changes to smbd as well. I may + * add this at a later date (JRA). + */ + + return 0; + } + } + } + + return servrec->serv.type; +} + +/******************************************************************* + Decide if we should write out a workgroup record for this workgroup. + We return zero if we should not. Don't write out lp_workgroup() (we've + already done it) and also don't write out a second workgroup record + on the unicast subnet that we've already written out on one of the + broadcast subnets. +******************************************************************/ + +static uint32 write_this_workgroup_name( struct subnet_record *subrec, + struct work_record *work) +{ + struct subnet_record *ssub; + + if(strequal(lp_workgroup(), work->work_group)) + return 0; + + /* This is a workgroup we have seen on a broadcast subnet. All + these have the same type. */ + + if(subrec != unicast_subnet) + return (SV_TYPE_DOMAIN_ENUM|SV_TYPE_NT|SV_TYPE_LOCAL_LIST_ONLY); + + for(ssub = FIRST_SUBNET; ssub; ssub = NEXT_SUBNET_EXCLUDING_UNICAST(ssub)) + { + /* This is the unicast subnet so check if we've already written out + this subnet when we passed over the broadcast subnets. */ + + if(find_workgroup_on_subnet( ssub, work->work_group) != NULL) + return 0; + } + + /* All workgroups on the unicast subnet (except our own, which we + have already written out) cannot be local. */ + + return (SV_TYPE_DOMAIN_ENUM|SV_TYPE_NT); +} + +/******************************************************************* + Write out the browse.dat file. + ******************************************************************/ + +void write_browse_list_entry(XFILE *fp, const char *name, uint32 rec_type, + const char *local_master_browser_name, const char *description) +{ + fstring tmp; + + slprintf(tmp,sizeof(tmp)-1, "\"%s\"", name); + x_fprintf(fp, "%-25s ", tmp); + x_fprintf(fp, "%08x ", rec_type); + slprintf(tmp, sizeof(tmp)-1, "\"%s\" ", local_master_browser_name); + x_fprintf(fp, "%-30s", tmp); + x_fprintf(fp, "\"%s\"\n", description); +} + +void write_browse_list(time_t t, BOOL force_write) +{ + struct subnet_record *subrec; + struct work_record *work; + struct server_record *servrec; + pstring fname,fnamenew; + uint32 stype; + int i; + XFILE *fp; + BOOL list_changed = force_write; + static time_t lasttime = 0; + + /* Always dump if we're being told to by a signal. */ + if(force_write == False) + { + if (!lasttime) + lasttime = t; + if (t - lasttime < 5) + return; + } + + lasttime = t; + + dump_workgroups(force_write); + + for (subrec = FIRST_SUBNET; subrec ; subrec = NEXT_SUBNET_INCLUDING_UNICAST(subrec)) + { + if(subrec->work_changed) + { + list_changed = True; + break; + } + } + + if(!list_changed) + return; + + updatecount++; + + pstrcpy(fname,lp_lockdir()); + trim_string(fname,NULL,"/"); + pstrcat(fname,"/"); + pstrcat(fname,SERVER_LIST); + pstrcpy(fnamenew,fname); + pstrcat(fnamenew,"."); + + fp = x_fopen(fnamenew,O_WRONLY|O_CREAT|O_TRUNC, 0644); + + if (!fp) + { + DEBUG(0,("write_browse_list: Can't open file %s. Error was %s\n", + fnamenew,strerror(errno))); + return; + } + + /* + * Write out a record for our workgroup. Use the record from the first + * subnet. + */ + + if((work = find_workgroup_on_subnet(FIRST_SUBNET, lp_workgroup())) == NULL) + { + DEBUG(0,("write_browse_list: Fatal error - cannot find my workgroup %s\n", + lp_workgroup())); + x_fclose(fp); + return; + } + + write_browse_list_entry(fp, work->work_group, + SV_TYPE_DOMAIN_ENUM|SV_TYPE_NT|SV_TYPE_LOCAL_LIST_ONLY, + work->local_master_browser_name, work->work_group); + + /* + * We need to do something special for our own names. + * This is due to the fact that we may be a local master browser on + * one of our broadcast subnets, and a domain master on the unicast + * subnet. We iterate over the subnets and only write out the name + * once. + */ + + for (i=0; my_netbios_names(i); i++) + { + stype = 0; + for (subrec = FIRST_SUBNET; subrec ; subrec = NEXT_SUBNET_INCLUDING_UNICAST(subrec)) + { + if((work = find_workgroup_on_subnet( subrec, lp_workgroup() )) == NULL) + continue; + if((servrec = find_server_in_workgroup( work, my_netbios_names(i))) == NULL) + continue; + + stype |= servrec->serv.type; + } + + /* Output server details, plus what workgroup they're in. */ + write_browse_list_entry(fp, my_netbios_names(i), stype, + string_truncate(lp_serverstring(), MAX_SERVER_STRING_LENGTH), lp_workgroup()); + } + + for (subrec = FIRST_SUBNET; subrec ; subrec = NEXT_SUBNET_INCLUDING_UNICAST(subrec)) + { + subrec->work_changed = False; + + for (work = subrec->workgrouplist; work ; work = work->next) + { + /* Write out a workgroup record for a workgroup. */ + uint32 wg_type = write_this_workgroup_name( subrec, work); + + if(wg_type) + { + write_browse_list_entry(fp, work->work_group, wg_type, + work->local_master_browser_name, + work->work_group); + } + + /* Now write out any server records a workgroup may have. */ + + for (servrec = work->serverlist; servrec ; servrec = servrec->next) + { + uint32 serv_type; + + /* We have already written our names here. */ + if(is_myname(servrec->serv.name)) + continue; + + serv_type = write_this_server_name(subrec, work, servrec); + + if(serv_type) + { + /* Output server details, plus what workgroup they're in. */ + write_browse_list_entry(fp, servrec->serv.name, serv_type, + servrec->serv.comment, work->work_group); + } + } + } + } + + x_fclose(fp); + unlink(fname); + chmod(fnamenew,0644); + rename(fnamenew,fname); + DEBUG(3,("write_browse_list: Wrote browse list into file %s\n",fname)); +} diff --git a/source4/nmbd/nmbd_subnetdb.c b/source4/nmbd/nmbd_subnetdb.c new file mode 100644 index 0000000000..6296826425 --- /dev/null +++ b/source4/nmbd/nmbd_subnetdb.c @@ -0,0 +1,361 @@ +/* + Unix SMB/CIFS implementation. + NBT netbios routines and daemon - version 2 + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Luke Kenneth Casson Leighton 1994-1998 + Copyright (C) Jeremy Allison 1994-1998 + + 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. + + Revision History: + +*/ + +#include "includes.h" + +extern int ClientNMB; +extern int ClientDGRAM; +extern int global_nmb_port; + +/* This is the broadcast subnets database. */ +struct subnet_record *subnetlist = NULL; + +/* Extra subnets - keep these separate so enumeration code doesn't + run onto it by mistake. */ + +struct subnet_record *unicast_subnet = NULL; +struct subnet_record *remote_broadcast_subnet = NULL; +struct subnet_record *wins_server_subnet = NULL; + +extern uint16 samba_nb_type; /* Samba's NetBIOS name type. */ + +/**************************************************************************** + Add a subnet into the list. + **************************************************************************/ + +static void add_subnet(struct subnet_record *subrec) +{ + DLIST_ADD(subnetlist, subrec); +} + +/* ************************************************************************** ** + * Comparison routine for ordering the splay-tree based namelists assoicated + * with each subnet record. + * + * Input: Item - Pointer to the comparison key. + * Node - Pointer to a node the splay tree. + * + * Output: The return value will be <0 , ==0, or >0 depending upon the + * ordinal relationship of the two keys. + * + * ************************************************************************** ** + */ +static int namelist_entry_compare( ubi_trItemPtr Item, ubi_trNodePtr Node ) + { + struct name_record *NR = (struct name_record *)Node; + + if( DEBUGLVL( 10 ) ) + { + struct nmb_name *Iname = (struct nmb_name *)Item; + + Debug1( "nmbd_subnetdb:namelist_entry_compare()\n" ); + Debug1( "%d == memcmp( \"%s\", \"%s\", %d )\n", + memcmp( Item, &(NR->name), sizeof(struct nmb_name) ), + nmb_namestr(Iname), nmb_namestr(&NR->name), (int)sizeof(struct nmb_name) ); + } + + return( memcmp( Item, &(NR->name), sizeof(struct nmb_name) ) ); + } /* namelist_entry_compare */ + + +/**************************************************************************** +stop listening on a subnet +we don't free the record as we don't have proper reference counting for it +yet and it may be in use by a response record + ****************************************************************************/ +void close_subnet(struct subnet_record *subrec) +{ + DLIST_REMOVE(subnetlist, subrec); + + if (subrec->dgram_sock != -1) { + close(subrec->dgram_sock); + subrec->dgram_sock = -1; + } + if (subrec->nmb_sock != -1) { + close(subrec->nmb_sock); + subrec->nmb_sock = -1; + } +} + + + +/**************************************************************************** + Create a subnet entry. + ****************************************************************************/ + +static struct subnet_record *make_subnet(const char *name, enum subnet_type type, + struct in_addr myip, struct in_addr bcast_ip, + struct in_addr mask_ip) +{ + struct subnet_record *subrec = NULL; + int nmb_sock, dgram_sock; + + /* Check if we are creating a non broadcast subnet - if so don't create + sockets. + */ + + if(type != NORMAL_SUBNET) + { + nmb_sock = -1; + dgram_sock = -1; + } + else + { + /* + * Attempt to open the sockets on port 137/138 for this interface + * and bind them. + * Fail the subnet creation if this fails. + */ + + if((nmb_sock = open_socket_in(SOCK_DGRAM, global_nmb_port,0, myip.s_addr,True)) == -1) + { + if( DEBUGLVL( 0 ) ) + { + Debug1( "nmbd_subnetdb:make_subnet()\n" ); + Debug1( " Failed to open nmb socket on interface %s ", inet_ntoa(myip) ); + Debug1( "for port %d. ", global_nmb_port ); + Debug1( "Error was %s\n", strerror(errno) ); + } + return NULL; + } + + if((dgram_sock = open_socket_in(SOCK_DGRAM,DGRAM_PORT,3, myip.s_addr,True)) == -1) + { + if( DEBUGLVL( 0 ) ) + { + Debug1( "nmbd_subnetdb:make_subnet()\n" ); + Debug1( " Failed to open dgram socket on interface %s ", inet_ntoa(myip) ); + Debug1( "for port %d. ", DGRAM_PORT ); + Debug1( "Error was %s\n", strerror(errno) ); + } + return NULL; + } + + /* Make sure we can broadcast from these sockets. */ + set_socket_options(nmb_sock,"SO_BROADCAST"); + set_socket_options(dgram_sock,"SO_BROADCAST"); + + } + + subrec = (struct subnet_record *)malloc(sizeof(*subrec)); + + if (!subrec) + { + DEBUG(0,("make_subnet: malloc fail !\n")); + close(nmb_sock); + close(dgram_sock); + return(NULL); + } + + memset( (char *)subrec, '\0', sizeof(*subrec) ); + (void)ubi_trInitTree( subrec->namelist, + namelist_entry_compare, + ubi_trOVERWRITE ); + + if((subrec->subnet_name = strdup(name)) == NULL) + { + DEBUG(0,("make_subnet: malloc fail for subnet name !\n")); + close(nmb_sock); + close(dgram_sock); + ZERO_STRUCTP(subrec); + SAFE_FREE(subrec); + return(NULL); + } + + DEBUG(2, ("making subnet name:%s ", name )); + DEBUG(2, ("Broadcast address:%s ", inet_ntoa(bcast_ip))); + DEBUG(2, ("Subnet mask:%s\n", inet_ntoa(mask_ip))); + + subrec->namelist_changed = False; + subrec->work_changed = False; + + subrec->bcast_ip = bcast_ip; + subrec->mask_ip = mask_ip; + subrec->myip = myip; + subrec->type = type; + subrec->nmb_sock = nmb_sock; + subrec->dgram_sock = dgram_sock; + + return subrec; +} + + +/**************************************************************************** + Create a normal subnet +**************************************************************************/ +struct subnet_record *make_normal_subnet(struct interface *iface) +{ + struct subnet_record *subrec; + + subrec = make_subnet(inet_ntoa(iface->ip), NORMAL_SUBNET, + iface->ip, iface->bcast, iface->nmask); + if (subrec) { + add_subnet(subrec); + } + return subrec; +} + + +/**************************************************************************** + Create subnet entries. +**************************************************************************/ + +BOOL create_subnets(void) +{ + int num_interfaces = iface_count(); + int i; + struct in_addr unicast_ip, ipzero; + extern struct in_addr loopback_ip; + + if(num_interfaces == 0) { + DEBUG(0,("create_subnets: No local interfaces !\n")); + DEBUG(0,("create_subnets: Waiting for an interface to appear ...\n")); + while (iface_count() == 0) { + sleep(5); + load_interfaces(); + } + } + + num_interfaces = iface_count(); + + /* + * Create subnets from all the local interfaces and thread them onto + * the linked list. + */ + + for (i = 0 ; i < num_interfaces; i++) + { + struct interface *iface = get_interface(i); + + /* + * We don't want to add a loopback interface, in case + * someone has added 127.0.0.1 for smbd, nmbd needs to + * ignore it here. JRA. + */ + + if (ip_equal(iface->ip, loopback_ip)) { + DEBUG(2,("create_subnets: Ignoring loopback interface.\n" )); + continue; + } + + if (!make_normal_subnet(iface)) return False; + } + + if (lp_we_are_a_wins_server()) { + /* Pick the first interface ip address as the WINS server ip. */ + unicast_ip = *iface_n_ip(0); + } else { + /* note that we do not set the wins server IP here. We just + set it at zero and let the wins registration code cope + with getting the IPs right for each packet */ + zero_ip(&unicast_ip); + } + + /* + * Create the unicast and remote broadcast subnets. + * Don't put these onto the linked list. + * The ip address of the unicast subnet is set to be + * the WINS server address, if it exists, or ipzero if not. + */ + + unicast_subnet = make_subnet( "UNICAST_SUBNET", UNICAST_SUBNET, + unicast_ip, unicast_ip, unicast_ip); + + zero_ip(&ipzero); + + remote_broadcast_subnet = make_subnet( "REMOTE_BROADCAST_SUBNET", + REMOTE_BROADCAST_SUBNET, + ipzero, ipzero, ipzero); + + if((unicast_subnet == NULL) || (remote_broadcast_subnet == NULL)) + return False; + + /* + * If we are WINS server, create the WINS_SERVER_SUBNET - don't put on + * the linked list. + */ + + if (lp_we_are_a_wins_server()) + { + if( (wins_server_subnet = make_subnet( "WINS_SERVER_SUBNET", + WINS_SERVER_SUBNET, + ipzero, ipzero, ipzero )) == NULL ) + return False; + } + + return True; +} + +/******************************************************************* +Function to tell us if we can use the unicast subnet. +******************************************************************/ +BOOL we_are_a_wins_client(void) +{ + if (wins_srv_count() > 0) { + return True; + } + + return False; +} + +/******************************************************************* +Access function used by NEXT_SUBNET_INCLUDING_UNICAST +******************************************************************/ + +struct subnet_record *get_next_subnet_maybe_unicast(struct subnet_record *subrec) +{ + if(subrec == unicast_subnet) + return NULL; + else if((subrec->next == NULL) && we_are_a_wins_client()) + return unicast_subnet; + else + return subrec->next; +} + +/******************************************************************* + Access function used by retransmit_or_expire_response_records() in + nmbd_packets.c. Patch from Andrey Alekseyev <fetch@muffin.arcadia.spb.ru> + Needed when we need to enumerate all the broadcast, unicast and + WINS subnets. +******************************************************************/ + +struct subnet_record *get_next_subnet_maybe_unicast_or_wins_server(struct subnet_record *subrec) +{ + if(subrec == unicast_subnet) + { + if(wins_server_subnet) + return wins_server_subnet; + else + return NULL; + } + + if(wins_server_subnet && subrec == wins_server_subnet) + return NULL; + + if((subrec->next == NULL) && we_are_a_wins_client()) + return unicast_subnet; + else + return subrec->next; +} diff --git a/source4/nmbd/nmbd_synclists.c b/source4/nmbd/nmbd_synclists.c new file mode 100644 index 0000000000..b9952fb446 --- /dev/null +++ b/source4/nmbd/nmbd_synclists.c @@ -0,0 +1,300 @@ +/* + Unix SMB/CIFS implementation. + NBT netbios routines and daemon - version 2 + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Luke Kenneth Casson Leighton 1994-1998 + Copyright (C) Jeremy Allison 1994-1998 + + 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. + +*/ + +/* this file handles asynchronous browse synchronisation requests. The + requests are done by forking and putting the result in a file in the + locks directory. We do it this way because we don't want nmbd to be + blocked waiting for some server to respond on a TCP connection. This + also allows us to have more than 1 sync going at once (tridge) */ + +#include "includes.h" + +struct sync_record { + struct sync_record *next, *prev; + fstring workgroup; + fstring server; + pstring fname; + struct in_addr ip; + pid_t pid; +}; + +/* a linked list of current sync connections */ +static struct sync_record *syncs; + +static XFILE *fp; + +/******************************************************************* + This is the NetServerEnum callback. + Note sname and comment are in UNIX codepage format. + ******************************************************************/ +static void callback(const char *sname, uint32 stype, + const char *comment, void *state) +{ + x_fprintf(fp,"\"%s\" %08X \"%s\"\n", sname, stype, comment); +} + +/******************************************************************* + Synchronise browse lists with another browse server. + Log in on the remote server's SMB port to their IPC$ service, + do a NetServerEnum and record the results in fname +******************************************************************/ +static void sync_child(char *name, int nm_type, + char *workgroup, + struct in_addr ip, BOOL local, BOOL servers, + char *fname) +{ + extern fstring local_machine; + fstring unix_workgroup; + static struct cli_state cli; + uint32 local_type = local ? SV_TYPE_LOCAL_LIST_ONLY : 0; + struct nmb_name called, calling; + + /* W2K DMB's return empty browse lists on port 445. Use 139. + * Patch from Andy Levine andyl@epicrealm.com. + */ + + if (!cli_initialise(&cli) || !cli_set_port(&cli, 139) || !cli_connect(&cli, name, &ip)) { + return; + } + + make_nmb_name(&calling, local_machine, 0x0); + make_nmb_name(&called , name , nm_type); + + if (!cli_session_request(&cli, &calling, &called)) + { + cli_shutdown(&cli); + return; + } + + if (!cli_negprot(&cli)) { + cli_shutdown(&cli); + return; + } + + if (!cli_session_setup(&cli, "", "", 1, "", 0, workgroup)) { + cli_shutdown(&cli); + return; + } + + if (!cli_send_tconX(&cli, "IPC$", "IPC", "", 1)) { + cli_shutdown(&cli); + return; + } + + /* All the cli_XX functions take UNIX character set. */ + fstrcpy(unix_workgroup, cli.server_domain?cli.server_domain:workgroup); + + /* Fetch a workgroup list. */ + cli_NetServerEnum(&cli, unix_workgroup, + local_type|SV_TYPE_DOMAIN_ENUM, + callback, NULL); + + /* Now fetch a server list. */ + if (servers) { + fstrcpy(unix_workgroup, workgroup); + cli_NetServerEnum(&cli, unix_workgroup, + local?SV_TYPE_LOCAL_LIST_ONLY:SV_TYPE_ALL, + callback, NULL); + } + + cli_shutdown(&cli); +} + + +/******************************************************************* + initialise a browse sync with another browse server. Log in on the + remote server's SMB port to their IPC$ service, do a NetServerEnum + and record the results +******************************************************************/ +void sync_browse_lists(struct work_record *work, + char *name, int nm_type, + struct in_addr ip, BOOL local, BOOL servers) +{ + struct sync_record *s; + static int counter; + + START_PROFILE(sync_browse_lists); + /* Check we're not trying to sync with ourselves. This can + happen if we are a domain *and* a local master browser. */ + if (ismyip(ip)) { +done: + END_PROFILE(sync_browse_lists); + return; + } + + s = (struct sync_record *)malloc(sizeof(*s)); + if (!s) goto done; + + ZERO_STRUCTP(s); + + fstrcpy(s->workgroup, work->work_group); + fstrcpy(s->server, name); + s->ip = ip; + + slprintf(s->fname, sizeof(pstring)-1, + "%s/sync.%d", lp_lockdir(), counter++); + all_string_sub(s->fname,"//", "/", 0); + + DLIST_ADD(syncs, s); + + /* the parent forks and returns, leaving the child to do the + actual sync and call END_PROFILE*/ + CatchChild(); + if ((s->pid = sys_fork())) return; + + BlockSignals( False, SIGTERM ); + + DEBUG(2,("Initiating browse sync for %s to %s(%s)\n", + work->work_group, name, inet_ntoa(ip))); + + fp = x_fopen(s->fname,O_WRONLY|O_CREAT|O_TRUNC, 0644); + if (!fp) { + END_PROFILE(sync_browse_lists); + _exit(1); + } + + sync_child(name, nm_type, work->work_group, ip, local, servers, + s->fname); + + x_fclose(fp); + END_PROFILE(sync_browse_lists); + _exit(0); +} + +/********************************************************************** +handle one line from a completed sync file + **********************************************************************/ +static void complete_one(struct sync_record *s, + char *sname, uint32 stype, char *comment) +{ + struct work_record *work; + struct server_record *servrec; + + stype &= ~SV_TYPE_LOCAL_LIST_ONLY; + + if (stype & SV_TYPE_DOMAIN_ENUM) { + /* See if we can find the workgroup on this subnet. */ + if((work=find_workgroup_on_subnet(unicast_subnet, sname))) { + /* We already know about this workgroup - + update the ttl. */ + update_workgroup_ttl(work,lp_max_ttl()); + } else { + /* Create the workgroup on the subnet. */ + work = create_workgroup_on_subnet(unicast_subnet, + sname, lp_max_ttl()); + if (work) { + /* remember who the master is */ + fstrcpy(work->local_master_browser_name, + comment); + } + } + return; + } + + work = find_workgroup_on_subnet(unicast_subnet, s->workgroup); + if (!work) { + DEBUG(3,("workgroup %s doesn't exist on unicast subnet?\n", + s->workgroup)); + return; + } + + if ((servrec = find_server_in_workgroup( work, sname))) { + /* Check that this is not a locally known + server - if so ignore the entry. */ + if(!(servrec->serv.type & SV_TYPE_LOCAL_LIST_ONLY)) { + /* We already know about this server - update + the ttl. */ + update_server_ttl(servrec, lp_max_ttl()); + /* Update the type. */ + servrec->serv.type = stype; + } + return; + } + + /* Create the server in the workgroup. */ + create_server_on_workgroup(work, sname,stype, lp_max_ttl(), comment); +} + + +/********************************************************************** +read the completed sync info + **********************************************************************/ +static void complete_sync(struct sync_record *s) +{ + XFILE *f; + fstring server, type_str; + unsigned type; + pstring comment; + pstring line; + const char *ptr; + int count=0; + + f = x_fopen(s->fname,O_RDONLY, 0); + + if (!f) return; + + while (!x_feof(f)) { + + if (!fgets_slash(line,sizeof(pstring),f)) continue; + + ptr = line; + + if (!next_token(&ptr,server,NULL,sizeof(server)) || + !next_token(&ptr,type_str,NULL, sizeof(type_str)) || + !next_token(&ptr,comment,NULL, sizeof(comment))) { + continue; + } + + sscanf(type_str, "%X", &type); + + complete_one(s, server, type, comment); + + count++; + } + + x_fclose(f); + + unlink(s->fname); + + DEBUG(2,("sync with %s(%s) for workgroup %s completed (%d records)\n", + s->server, inet_ntoa(s->ip), s->workgroup, count)); +} + +/********************************************************************** +check for completion of any of the child processes + **********************************************************************/ +void sync_check_completion(void) +{ + struct sync_record *s, *next; + + for (s=syncs;s;s=next) { + next = s->next; + if (!process_exists(s->pid)) { + /* it has completed - grab the info */ + complete_sync(s); + DLIST_REMOVE(syncs, s); + ZERO_STRUCTP(s); + SAFE_FREE(s); + } + } +} diff --git a/source4/nmbd/nmbd_winsproxy.c b/source4/nmbd/nmbd_winsproxy.c new file mode 100644 index 0000000000..2e65ebb612 --- /dev/null +++ b/source4/nmbd/nmbd_winsproxy.c @@ -0,0 +1,221 @@ +/* + Unix SMB/CIFS implementation. + NBT netbios routines and daemon - version 2 + + Copyright (C) Jeremy Allison 1994-1998 + + 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" + +/**************************************************************************** +Function called when the name lookup succeeded. +****************************************************************************/ + +static void wins_proxy_name_query_request_success( struct subnet_record *subrec, + struct userdata_struct *userdata, + struct nmb_name *nmbname, struct in_addr ip, struct res_rec *rrec) +{ + struct packet_struct *original_packet; + struct subnet_record *orig_broadcast_subnet; + struct name_record *namerec; + uint16 nb_flags; + int num_ips; + int i; + int ttl = 3600; /* By default one hour in the cache. */ + struct in_addr *iplist; + + /* Extract the original packet and the original broadcast subnet from + the userdata. */ + + memcpy( (char *)&orig_broadcast_subnet, userdata->data, sizeof(struct subnet_record *) ); + memcpy( (char *)&original_packet, &userdata->data[sizeof(struct subnet_record *)], + sizeof(struct packet_struct *) ); + + nb_flags = get_nb_flags( rrec->rdata ); + + num_ips = rrec->rdlength / 6; + if(num_ips == 0) + { + DEBUG(0,("wins_proxy_name_query_request_success: Invalid number of IP records (0) \ +returned for name %s.\n", nmb_namestr(nmbname) )); + return; + } + + if(num_ips == 1) + iplist = &ip; + else + { + if((iplist = (struct in_addr *)malloc( num_ips * sizeof(struct in_addr) )) == NULL) + { + DEBUG(0,("wins_proxy_name_query_request_success: malloc fail !\n")); + return; + } + + for(i = 0; i < num_ips; i++) + putip( (char *)&iplist[i], (char *)&rrec->rdata[ (i*6) + 2]); + } + + /* Add the queried name to the original subnet as a WINS_PROXY_NAME. */ + + if(rrec == PERMANENT_TTL) + ttl = lp_max_ttl(); + + namerec = add_name_to_subnet( orig_broadcast_subnet, nmbname->name, + nmbname->name_type, nb_flags, ttl, + WINS_PROXY_NAME, num_ips, iplist ); + + if(iplist != &ip) + SAFE_FREE(iplist); + + /* + * Check that none of the IP addresses we are returning is on the + * same broadcast subnet as the original requesting packet. If it + * is then don't reply (although we still need to add the name + * to the cache) as the actual machine will be replying also + * and we don't want two replies to a broadcast query. + */ + + if(namerec && original_packet->packet.nmb.header.nm_flags.bcast) + { + for( i = 0; i < namerec->data.num_ips; i++) + { + if( same_net( namerec->data.ip[i], + orig_broadcast_subnet->myip, + orig_broadcast_subnet->mask_ip ) ) + { + DEBUG( 5, ( "wins_proxy_name_query_request_success: name %s is a WINS \ +proxy name and is also on the same subnet (%s) as the requestor. \ +Not replying.\n", + nmb_namestr(&namerec->name), + orig_broadcast_subnet->subnet_name ) ); + return; + } + } + } + + /* Finally reply to the original name query. */ + reply_netbios_packet(original_packet, /* Packet to reply to. */ + 0, /* Result code. */ + NMB_QUERY, /* nmbd type code. */ + NMB_NAME_QUERY_OPCODE, /* opcode. */ + ttl, /* ttl. */ + rrec->rdata, /* data to send. */ + rrec->rdlength); /* data length. */ +} + +/**************************************************************************** +Function called when the name lookup failed. +****************************************************************************/ + +static void wins_proxy_name_query_request_fail(struct subnet_record *subrec, + struct response_record *rrec, + struct nmb_name *question_name, int fail_code) +{ + DEBUG(4,("wins_proxy_name_query_request_fail: WINS server returned error code %d for lookup \ +of name %s.\n", fail_code, nmb_namestr(question_name) )); +} + +/**************************************************************************** +Function to make a deep copy of the userdata we will need when the WINS +proxy query returns. +****************************************************************************/ + +static struct userdata_struct *wins_proxy_userdata_copy_fn(struct userdata_struct *userdata) +{ + struct packet_struct *p, *copy_of_p; + struct userdata_struct *new_userdata = + (struct userdata_struct *)malloc( userdata->userdata_len ); + + if(new_userdata == NULL) + return NULL; + + new_userdata->copy_fn = userdata->copy_fn; + new_userdata->free_fn = userdata->free_fn; + new_userdata->userdata_len = userdata->userdata_len; + + /* Copy the subnet_record pointer. */ + memcpy( new_userdata->data, userdata->data, sizeof(struct subnet_record *) ); + + /* Extract the pointer to the packet struct */ + memcpy((char *)&p, &userdata->data[sizeof(struct subnet_record *)], + sizeof(struct packet_struct *) ); + + /* Do a deep copy of the packet. */ + if((copy_of_p = copy_packet(p)) == NULL) + { + SAFE_FREE(new_userdata); + return NULL; + } + + /* Lock the copy. */ + copy_of_p->locked = True; + + memcpy( &new_userdata->data[sizeof(struct subnet_record *)], (char *)©_of_p, + sizeof(struct packet_struct *) ); + + return new_userdata; +} + +/**************************************************************************** +Function to free the deep copy of the userdata we used when the WINS +proxy query returned. +****************************************************************************/ + +static void wins_proxy_userdata_free_fn(struct userdata_struct *userdata) +{ + struct packet_struct *p; + + /* Extract the pointer to the packet struct */ + memcpy((char *)&p, &userdata->data[sizeof(struct subnet_record *)], + sizeof(struct packet_struct *)); + + /* Unlock the packet. */ + p->locked = False; + + free_packet(p); + ZERO_STRUCTP(userdata); + SAFE_FREE(userdata); +} + +/**************************************************************************** + Make a WINS query on behalf of a broadcast client name query request. +****************************************************************************/ + +void make_wins_proxy_name_query_request( struct subnet_record *subrec, + struct packet_struct *incoming_packet, + struct nmb_name *question_name) +{ + long *ud[(sizeof(struct userdata_struct) + sizeof(struct subrec *) + + sizeof(struct packet_struct *))/sizeof(long *) + 1]; + struct userdata_struct *userdata = (struct userdata_struct *)ud; + + memset(ud, '\0', sizeof(ud)); + + userdata->copy_fn = wins_proxy_userdata_copy_fn; + userdata->free_fn = wins_proxy_userdata_free_fn; + userdata->userdata_len = sizeof(ud); + memcpy( userdata->data, (char *)&subrec, sizeof(struct subnet_record *)); + memcpy( &userdata->data[sizeof(struct subnet_record *)], (char *)&incoming_packet, + sizeof(struct packet_struct *)); + + /* Now use the unicast subnet to query the name with the WINS server. */ + query_name( unicast_subnet, question_name->name, question_name->name_type, + wins_proxy_name_query_request_success, + wins_proxy_name_query_request_fail, + userdata); +} diff --git a/source4/nmbd/nmbd_winsserver.c b/source4/nmbd/nmbd_winsserver.c new file mode 100644 index 0000000000..4ef476f814 --- /dev/null +++ b/source4/nmbd/nmbd_winsserver.c @@ -0,0 +1,2032 @@ +/* + Unix SMB/CIFS implementation. + NBT netbios routines and daemon - version 2 + + Copyright (C) Jeremy Allison 1994-1998 + + 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.tdb" +#define WINS_VERSION 1 + +/**************************************************************************** +change the wins owner address in the record. +*****************************************************************************/ +static void update_wins_owner(struct name_record *namerec, struct in_addr wins_ip) +{ + if (namerec==NULL) + return; + 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) +{ + if (namerec==NULL) + return; + + 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: 0x%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(SMB_BIG_UINT *current_id, BOOL update) +{ + /* + * it's kept as a static here, to prevent people from messing + * with the value directly + */ + + static SMB_BIG_UINT 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 +*****************************************************************************/ +static void wins_hook(const char *operation, struct name_record *namerec, int ttl) +{ + pstring command; + char *cmd = lp_wins_hook(); + char *p; + int i; + + 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; + } + } + + p = command; + p += slprintf(p, sizeof(command)-1, "%s %s %s %02x %d", + cmd, + operation, + namerec->name.name, + namerec->name.name_type, + ttl); + + for (i=0;i<namerec->data.num_ips;i++) { + p += slprintf(p, sizeof(command) - (p-command) -1, " %s", inet_ntoa(namerec->data.ip[i])); + } + + DEBUG(3,("calling wins hook for %s\n", nmb_namestr(&namerec->name))); + smbrun(command, NULL); +} + + +/**************************************************************************** +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); + TDB_CONTEXT *tdb; + TDB_DATA kbuf, dbuf, newkey; + struct name_record *namerec = NULL; + struct in_addr our_fake_ip = *interpret_addr2("0.0.0.0"); + + DEBUG(2,("initialise_wins: started\n")); + + if(!lp_we_are_a_wins_server()) + return True; + + add_samba_names_to_subnet(wins_server_subnet); + + tdb = tdb_open_log(lock_path(WINS_LIST), 0, TDB_DEFAULT, O_RDONLY, 0600); + if (!tdb) { + DEBUG(2,("initialise_wins: Can't open wins database file %s. Error was %s\n", WINS_LIST, strerror(errno) )); + return True; + } + + if (tdb_fetch_int32(tdb, INFO_VERSION) != WINS_VERSION) { + DEBUG(0,("Discarding invalid wins.dat file\n")); + tdb_close(tdb); + return True; + } + + for (kbuf = tdb_firstkey(tdb); + kbuf.dptr; + newkey = tdb_nextkey(tdb, kbuf), safe_free(kbuf.dptr), kbuf=newkey) { + + fstring name_type; + pstring name, ip_str; + char *p; + int type = 0; + int nb_flags; + int ttl; + unsigned int num_ips; + int high, low; + struct in_addr wins_ip; + struct in_addr *ip_list; + int wins_flags; + int len,i; + + if (strncmp(kbuf.dptr, ENTRY_PREFIX, strlen(ENTRY_PREFIX)) != 0) + continue; + + dbuf = tdb_fetch(tdb, kbuf); + if (!dbuf.dptr) + continue; + + fstrcpy(name_type, kbuf.dptr+strlen(ENTRY_PREFIX)); + + pstrcpy(name, name_type); + + if((p = strchr(name,'#')) != NULL) { + *p = 0; + sscanf(p+1,"%x",&type); + } + + len = tdb_unpack(dbuf.dptr, dbuf.dsize, "dddfddd", + &nb_flags, &high, &low, + ip_str, &ttl, &num_ips, &wins_flags); + + wins_ip=*interpret_addr2(ip_str); + + /* Don't reload replica records */ + if (!ip_equal(wins_ip, our_fake_ip)) { + SAFE_FREE(dbuf.dptr); + continue; + } + + /* Don't reload released or tombstoned records */ + if ((wins_flags&WINS_STATE_MASK) != WINS_ACTIVE) { + SAFE_FREE(dbuf.dptr); + continue; + } + + /* Allocate the space for the ip_list. */ + if((ip_list = (struct in_addr *)malloc( num_ips * sizeof(struct in_addr))) == NULL) { + SAFE_FREE(dbuf.dptr); + DEBUG(0,("initialise_wins: Malloc fail !\n")); + return False; + } + + for (i = 0; i < num_ips; i++) { + len += tdb_unpack(dbuf.dptr+len, dbuf.dsize-len, "f", ip_str); + ip_list[i] = *interpret_addr2(ip_str); + } + + /* 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)); + + namerec=add_name_to_subnet( wins_server_subnet, name, type, nb_flags, + ttl, REGISTER_NAME, num_ips, ip_list); + if (namerec!=NULL) { + update_wins_owner(namerec, wins_ip); + update_wins_flag(namerec, wins_flags); + /* we don't reload the ID, on startup we restart at 1 */ + get_global_id_and_update(&namerec->data.id, True); + } + + } 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)); + } + + SAFE_FREE(dbuf.dptr); + SAFE_FREE(ip_list); + } + + tdb_close(tdb); + DEBUG(2,("initialise_wins: done\n")); + 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; + struct in_addr 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. + */ + + 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", + nmb_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", nmb_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 + * treat it like a registration request. This allows us to recover + * from errors (tridge) + */ + + if(namerec == NULL) + { + DEBUG(3,("wins_process_name_refresh_request: Name refresh for name %s and \ +the name does not exist. Treating as registration.\n", nmb_namestr(question) )); + wins_process_name_registration_request(subrec,p); + return; + } + + /* + * if the name is present but not active, + * simply remove it and treat the request + * as a registration + */ + if (namerec != NULL && !WINS_STATE_ACTIVE(namerec)) + { + DEBUG(5,("wins_process_name_refresh_request: Name (%s) in WINS was \ +not active - removing it.\n", nmb_namestr(question) )); + 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((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", nmb_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); + + /* + * if the record is a replica: + * we take ownership and update the version ID. + */ + if (!ip_equal(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) + { + /* + * 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", nmb_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), 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); +} + +/*********************************************************************** + 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 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->data.source == REGISTER_NAME) + && ip_equal(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) +{ + 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; + struct in_addr 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)) { + 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); + 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) && (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", 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); + send_wins_name_registration_response(0, ttl, p); + wins_hook("refresh", namerec, ttl); + return; + } + } + + /* + * 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( namerec->data.ip[0], from_ip ) + && ip_equal(namerec->data.wins_ip, our_fake_ip) ) + { + update_name_ttl( namerec, ttl ); + send_wins_name_registration_response( 0, ttl, p ); + wins_hook("refresh", namerec, ttl); + 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. + */ + + query_name_from_wins_server( *namerec->data.ip, + question->name, + question->name_type, + wins_register_query_success, + wins_register_query_fail, + userdata ); + return; + } + + /* + * Name did not exist - add it. + */ + + (void)add_name_to_subnet( subrec, question->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 = *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 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->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); + send_wins_name_registration_response(0, ttl, orig_reg_packet); + wins_hook("add", namerec, ttl); + + 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 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 = *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. Errror - 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.", 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(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. + */ + 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("add", namerec, ttl); + } else { + wins_hook("refresh", namerec, ttl); + } + + update_name_ttl(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(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); + } + + send_wins_name_registration_response(0, ttl, p); + wins_hook("refresh", namerec, ttl); + 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 + */ + + query_name_from_wins_server( namerec->data.ip[0], + question->name, + question->name_type, + wins_multihomed_register_query_success, + wins_multihomed_register_query_fail, + userdata ); + + return; + } + + /* + * Name did not exist - add it. + */ + + (void)add_name_to_subnet( subrec, question->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 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; + for( namerec = (struct name_record *)ubi_trFirst( subrec->namelist ); + namerec; + namerec = (struct name_record *)ubi_trNext( namerec ) ) + { + 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 *)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 = (struct name_record *)ubi_trFirst( subrec->namelist ); + namerec; + namerec = (struct name_record *)ubi_trNext( namerec ) ) + { + 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) + { + ttl = (namerec->data.death_time != PERMANENT_TTL) ? + namerec->data.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->data.num_ips == 1 ) + prdata = rdata; + else + { + if((prdata = (char *)malloc( namerec->data.num_ips * 6 )) == NULL) + { + DEBUG(0,("send_wins_name_query_response: malloc fail !\n")); + return; + } + } + + for(i = 0; i < namerec->data.num_ips; 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 = namerec->data.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) + 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; + + 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. + */ + + 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 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_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, &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", + 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.", 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 anymore active.\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))); + send_wins_name_release_response(0, p); + return; + } + + /* + * Send a release response. + * Flag the name as released and update the ttl + */ + + send_wins_name_release_response(0, p); + + namerec->data.wins_flags |= WINS_RELEASED; + update_name_ttl(namerec, EXTINCTION_INTERVAL); + + wins_hook("delete", namerec, 0); +} + +/******************************************************************* + WINS time dependent processing. +******************************************************************/ + +void initiate_wins_processing(time_t t) +{ + static time_t lasttime = 0; + struct name_record *namerec; + struct name_record *next_namerec; + struct in_addr our_fake_ip = *interpret_addr2("0.0.0.0"); + + if (!lasttime) + lasttime = t; + if (t - lasttime < 20) + return; + + lasttime = t; + + if(!lp_we_are_a_wins_server()) + return; + + for( namerec = (struct name_record *)ubi_trFirst( wins_server_subnet->namelist ); + namerec; + namerec = next_namerec ) { + next_namerec = (struct name_record *)ubi_trNext( namerec ); + + if( (namerec->data.death_time != PERMANENT_TTL) + && (namerec->data.death_time < t) ) { + + if( namerec->data.source == SELF_NAME ) { + DEBUG( 3, ( "expire_names_on_subnet: Subnet %s not expiring SELF name %s\n", + wins_server_subnet->subnet_name, nmb_namestr(&namerec->name) ) ); + namerec->data.death_time += 300; + namerec->subnet->namelist_changed = True; + continue; + } + + /* handle records, samba is the wins owner */ + if (ip_equal(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,("initiate_wins_processing: expiring %s\n", nmb_namestr(&namerec->name))); + break; + 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,("initiate_wins_processing: tombstoning %s\n", nmb_namestr(&namerec->name))); + break; + case WINS_TOMBSTONED: + DEBUG(3,("initiate_wins_processing: deleting %s\n", nmb_namestr(&namerec->name))); + remove_name_from_namelist( wins_server_subnet, namerec ); + break; + } + } 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,("initiate_wins_processing: tombstoning %s\n", nmb_namestr(&namerec->name))); + case WINS_TOMBSTONED: + DEBUG(3,("initiate_wins_processing: deleting %s\n", nmb_namestr(&namerec->name))); + remove_name_from_namelist( wins_server_subnet, namerec ); + break; + case WINS_RELEASED: + DEBUG(0,("initiate_wins_processing: %s is in released state and\ +we are not the wins owner !\n", nmb_namestr(&namerec->name))); + break; + } + } + + } + } + + if(wins_server_subnet->namelist_changed) + wins_write_database(True); + + wins_server_subnet->namelist_changed = False; +} + +/******************************************************************* + Write out the current WINS database. +******************************************************************/ +void wins_write_database(BOOL background) +{ + struct name_record *namerec; + pstring fname, fnamenew; + TDB_CONTEXT *tdb; + TDB_DATA kbuf, dbuf; + pstring key, buf; + int len; + int num_record=0; + SMB_BIG_UINT id; + + 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 (sys_fork()) { + return; + } + } + + slprintf(fname,sizeof(fname)-1,"%s/%s", lp_lockdir(), WINS_LIST); + all_string_sub(fname,"//", "/", 0); + slprintf(fnamenew,sizeof(fnamenew)-1,"%s.%u", fname, (unsigned int)sys_getpid()); + + tdb = tdb_open_log(fnamenew, 0, TDB_DEFAULT, O_RDWR|O_CREAT|O_TRUNC, 0644); + if (!tdb) { + DEBUG(0,("wins_write_database: Can't open %s. Error was %s\n", fnamenew, strerror(errno))); + if (background) + _exit(0); + return; + } + + DEBUG(3,("wins_write_database: Dump of WINS name list.\n")); + + tdb_store_int32(tdb, INFO_VERSION, WINS_VERSION); + + for (namerec = (struct name_record *)ubi_trFirst( wins_server_subnet->namelist ); + namerec; + namerec = (struct name_record *)ubi_trNext( namerec ) ) { + + int i; + struct tm *tm; + + DEBUGADD(3,("%-19s ", nmb_namestr(&namerec->name) )); + + if( namerec->data.death_time != PERMANENT_TTL ) { + char *ts, *nl; + + tm = LocalTime(&namerec->data.death_time); + ts = asctime(tm); + nl = strrchr_m( ts, '\n' ); + if( NULL != nl ) + *nl = '\0'; + + DEBUGADD(3,("TTL = %s ", ts )); + } else + DEBUGADD(3,("TTL = PERMANENT ")); + + for (i = 0; i < namerec->data.num_ips; i++) + DEBUGADD(0,("%15s ", inet_ntoa(namerec->data.ip[i]) )); + + DEBUGADD(3,("0x%2x 0x%2x %15s\n", namerec->data.nb_flags, namerec->data.wins_flags, inet_ntoa(namerec->data.wins_ip))); + + if( namerec->data.source == REGISTER_NAME ) { + + /* store the type in the key to make the name unique */ + slprintf(key, sizeof(key), "%s%s#%02x", ENTRY_PREFIX, namerec->name.name, namerec->name.name_type); + + len = tdb_pack(buf, sizeof(buf), "dddfddd", + (int)namerec->data.nb_flags, + (int)(namerec->data.id>>32), + (int)(namerec->data.id&0xffffffff), + inet_ntoa(namerec->data.wins_ip), + (int)namerec->data.death_time, + namerec->data.num_ips, + namerec->data.wins_flags); + + for (i = 0; i < namerec->data.num_ips; i++) + len += tdb_pack(buf+len, sizeof(buf)-len, "f", inet_ntoa(namerec->data.ip[i])); + + kbuf.dsize = strlen(key)+1; + kbuf.dptr = key; + dbuf.dsize = len; + dbuf.dptr = buf; + if (tdb_store(tdb, kbuf, dbuf, TDB_INSERT) != 0) return; + + num_record++; + } + } + + /* store the number of records */ + tdb_store_int32(tdb, INFO_COUNT, num_record); + + /* get and store the last used ID */ + get_global_id_and_update(&id, False); + tdb_store_int32(tdb, INFO_ID_HIGH, id>>32); + tdb_store_int32(tdb, INFO_ID_LOW, id&0xffffffff); + + tdb_close(tdb); + + chmod(fnamenew,0644); + unlink(fname); + rename(fnamenew,fname); + + if (background) + _exit(0); +} + +/**************************************************************************** +process a internal Samba message receiving a wins record +***************************************************************************/ +void nmbd_wins_new_entry(int msg_type, pid_t src, void *buf, size_t len) +{ + 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 = *interpret_addr2("0.0.0.0"); + int i; + + if (buf==NULL) + return; + + record=(WINS_RECORD *)buf; + + ZERO_STRUCT(question); + memcpy(question.name, record->name, 16); + question.name_type=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(namerec->data.wins_ip, our_fake_ip)) { + if (namerec->data.wins_flags&WINS_ACTIVE && record->wins_flags&WINS_TOMBSTONED) { + if (ip_equal(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(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; i<record->num_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(record->wins_ip, namerec->data.wins_ip)) + overwrite=True; + + if (ip_equal(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(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; + } + + } +} + + + + + + + + diff --git a/source4/nmbd/nmbd_workgroupdb.c b/source4/nmbd/nmbd_workgroupdb.c new file mode 100644 index 0000000000..3e177bceb4 --- /dev/null +++ b/source4/nmbd/nmbd_workgroupdb.c @@ -0,0 +1,342 @@ +/* + Unix SMB/CIFS implementation. + NBT netbios routines and daemon - version 2 + Copyright (C) Andrew Tridgell 1994-1998 + Copyright (C) Luke Kenneth Casson Leighton 1994-1998 + Copyright (C) Jeremy Allison 1994-1998 + + 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" + +extern int ClientNMB; + +extern uint16 samba_nb_type; + +int workgroup_count = 0; /* unique index key: one for each workgroup */ + +/**************************************************************************** + Add a workgroup into the list. + **************************************************************************/ + +static void add_workgroup(struct subnet_record *subrec, struct work_record *work) +{ + work->subnet = subrec; + DLIST_ADD(subrec->workgrouplist, work); + subrec->work_changed = True; +} + +/**************************************************************************** + Create an empty workgroup. + **************************************************************************/ + +static struct work_record *create_workgroup(const char *name, int ttl) +{ + struct work_record *work; + struct subnet_record *subrec; + int t = -1; + + if((work = (struct work_record *)malloc(sizeof(*work))) == NULL) + { + DEBUG(0,("create_workgroup: malloc fail !\n")); + return NULL; + } + memset((char *)work, '\0', sizeof(*work)); + + StrnCpy(work->work_group,name,sizeof(work->work_group)-1); + work->serverlist = NULL; + + work->RunningElection = False; + work->ElectionCount = 0; + work->announce_interval = 0; + work->needelection = False; + work->needannounce = True; + work->lastannounce_time = time(NULL); + work->mst_state = lp_local_master() ? MST_POTENTIAL : MST_NONE; + work->dom_state = DOMAIN_NONE; + work->log_state = LOGON_NONE; + + work->death_time = (ttl != PERMANENT_TTL) ? time(NULL)+(ttl*3) : PERMANENT_TTL; + + /* Make sure all token representations of workgroups are unique. */ + + for (subrec = FIRST_SUBNET; subrec && (t == -1); + subrec = NEXT_SUBNET_INCLUDING_UNICAST(subrec)) + { + struct work_record *w; + for (w = subrec->workgrouplist; w && t == -1; w = w->next) + { + if (strequal(w->work_group, work->work_group)) + t = w->token; + } + } + + if (t == -1) + work->token = ++workgroup_count; + else + work->token = t; + + /* No known local master browser as yet. */ + *work->local_master_browser_name = '\0'; + + /* No known domain master browser as yet. */ + *work->dmb_name.name = '\0'; + zero_ip(&work->dmb_addr); + + /* WfWg uses 01040b01 */ + /* Win95 uses 01041501 */ + /* NTAS uses ???????? */ + work->ElectionCriterion = (MAINTAIN_LIST)|(BROWSER_ELECTION_VERSION<<8); + work->ElectionCriterion |= (lp_os_level() << 24); + if (lp_domain_master()) + work->ElectionCriterion |= 0x80; + + return work; +} + +/******************************************************************* + Remove a workgroup. + ******************************************************************/ + +static struct work_record *remove_workgroup_from_subnet(struct subnet_record *subrec, + struct work_record *work) +{ + struct work_record *ret_work = NULL; + + DEBUG(3,("remove_workgroup: Removing workgroup %s\n", work->work_group)); + + ret_work = work->next; + + remove_all_servers(work); + + if (!work->serverlist) + { + if (work->prev) + work->prev->next = work->next; + if (work->next) + work->next->prev = work->prev; + + if (subrec->workgrouplist == work) + subrec->workgrouplist = work->next; + + ZERO_STRUCTP(work); + SAFE_FREE(work); + } + + subrec->work_changed = True; + + return ret_work; +} + + +/**************************************************************************** + Find a workgroup in the workgroup list of a subnet. + **************************************************************************/ + +struct work_record *find_workgroup_on_subnet(struct subnet_record *subrec, + const char *name) +{ + struct work_record *ret; + + DEBUG(4, ("find_workgroup_on_subnet: workgroup search for %s on subnet %s: ", + name, subrec->subnet_name)); + + for (ret = subrec->workgrouplist; ret; ret = ret->next) + { + if (!strcmp(ret->work_group,name)) + { + DEBUGADD(4, ("found.\n")); + return(ret); + } + } + DEBUGADD(4, ("not found.\n")); + return NULL; +} + +/**************************************************************************** + Create a workgroup in the workgroup list of the subnet. + **************************************************************************/ + +struct work_record *create_workgroup_on_subnet(struct subnet_record *subrec, + const char *name, int ttl) +{ + struct work_record *work = NULL; + + DEBUG(4,("create_workgroup_on_subnet: creating group %s on subnet %s\n", + name, subrec->subnet_name)); + + if ((work = create_workgroup(name, ttl))) + { + add_workgroup(subrec, work); + + subrec->work_changed = True; + + return(work); + } + + return NULL; +} + +/**************************************************************************** + Update a workgroup ttl. + **************************************************************************/ + +void update_workgroup_ttl(struct work_record *work, int ttl) +{ + if(work->death_time != PERMANENT_TTL) + work->death_time = time(NULL)+(ttl*3); + work->subnet->work_changed = True; +} + +/**************************************************************************** + Fail function called if we cannot register the WORKGROUP<0> and + WORKGROUP<1e> names on the net. +**************************************************************************/ + +static void fail_register(struct subnet_record *subrec, struct response_record *rrec, + struct nmb_name *nmbname) +{ + DEBUG(0,("fail_register: Failed to register name %s on subnet %s.\n", + nmb_namestr(nmbname), subrec->subnet_name)); +} + +/**************************************************************************** + If the workgroup is our primary workgroup, add the required names to it. +**************************************************************************/ + +void initiate_myworkgroup_startup(struct subnet_record *subrec, struct work_record *work) +{ + int i; + + if(!strequal(lp_workgroup(), work->work_group)) + return; + + /* If this is a broadcast subnet then start elections on it + if we are so configured. */ + + if ((subrec != unicast_subnet) && (subrec != remote_broadcast_subnet) && + (subrec != wins_server_subnet) && lp_preferred_master() && + lp_local_master()) + { + DEBUG(3, ("initiate_myworkgroup_startup: preferred master startup for \ +workgroup %s on subnet %s\n", work->work_group, subrec->subnet_name)); + work->needelection = True; + work->ElectionCriterion |= (1<<3); + } + + /* Register the WORKGROUP<0> and WORKGROUP<1e> names on the network. */ + + register_name(subrec,lp_workgroup(),0x0,samba_nb_type|NB_GROUP, + NULL, + fail_register,NULL); + + register_name(subrec,lp_workgroup(),0x1e,samba_nb_type|NB_GROUP, + NULL, + fail_register,NULL); + + for( i = 0; my_netbios_names(i); i++) + { + const char *name = my_netbios_names(i); + int stype = lp_default_server_announce() | (lp_local_master() ? + SV_TYPE_POTENTIAL_BROWSER : 0 ); + + if(!strequal(lp_netbios_name(), name)) + stype &= ~(SV_TYPE_MASTER_BROWSER|SV_TYPE_POTENTIAL_BROWSER| + SV_TYPE_DOMAIN_MASTER|SV_TYPE_DOMAIN_MEMBER); + + create_server_on_workgroup(work,name,stype|SV_TYPE_LOCAL_LIST_ONLY, + PERMANENT_TTL, + string_truncate(lp_serverstring(), MAX_SERVER_STRING_LENGTH)); + DEBUG(3,("initiate_myworkgroup_startup: Added server name entry %s \ +on subnet %s\n", name, subrec->subnet_name)); + } +} + +/**************************************************************************** + Dump a copy of the workgroup database into the log file. + **************************************************************************/ + +void dump_workgroups(BOOL force_write) +{ + struct subnet_record *subrec; + int debuglevel = force_write ? 0 : 4; + + for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_INCLUDING_UNICAST(subrec)) + { + if (subrec->workgrouplist) + { + struct work_record *work; + + if( DEBUGLVL( debuglevel ) ) + { + dbgtext( "dump_workgroups()\n " ); + dbgtext( "dump workgroup on subnet %15s: ", subrec->subnet_name ); + dbgtext( "netmask=%15s:\n", inet_ntoa(subrec->mask_ip) ); + } + + for (work = subrec->workgrouplist; work; work = work->next) + { + DEBUGADD( debuglevel, ( "\t%s(%d) current master browser = %s\n", + work->work_group, + work->token, + *work->local_master_browser_name + ? work->local_master_browser_name : "UNKNOWN" ) ); + if (work->serverlist) + { + struct server_record *servrec; + for (servrec = work->serverlist; servrec; servrec = servrec->next) + { + DEBUGADD( debuglevel, ( "\t\t%s %8x (%s)\n", + servrec->serv.name, + servrec->serv.type, + servrec->serv.comment ) ); + } + } + } + } + } +} + +/**************************************************************************** + Expire any dead servers on all workgroups. If the workgroup has expired + remove it. + **************************************************************************/ + +void expire_workgroups_and_servers(time_t t) +{ + struct subnet_record *subrec; + + for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_INCLUDING_UNICAST(subrec)) + { + struct work_record *work; + struct work_record *nextwork; + + for (work = subrec->workgrouplist; work; work = nextwork) + { + nextwork = work->next; + expire_servers(work, t); + + if ((work->serverlist == NULL) && (work->death_time != PERMANENT_TTL) && + ((t == -1) || (work->death_time < t))) + { + DEBUG(3,("expire_workgroups_and_servers: Removing timed out workgroup %s\n", + work->work_group)); + remove_workgroup_from_subnet(subrec, work); + } + } + } +} |