diff options
Diffstat (limited to 'source3/nmbd')
30 files changed, 16198 insertions, 0 deletions
diff --git a/source3/nmbd/asyncdns.c b/source3/nmbd/asyncdns.c new file mode 100644 index 0000000..4601ba6 --- /dev/null +++ b/source3/nmbd/asyncdns.c @@ -0,0 +1,346 @@ +/* + 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 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "includes.h" +#include "nmbd/nmbd.h" +#include "lib/util/sys_rw_data.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; + unstring qname; + + pull_ascii_nstring(qname, sizeof(qname), question->name); + + if (!addr.s_addr) { + /* add the fail to WINS cache of names. give it 1 hour in the cache */ + DBG_INFO("add_dns_result: Negative DNS answer for %s\n", qname); + 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 */ + DBG_INFO("add_dns_result: DNS gave answer for %s of %s\n", qname, inet_ntoa(addr)); + + add_name_to_subnet( wins_server_subnet, qname, name_type, + NB_ACTIVE, 2*60*60, DNS_NAME, 1, &addr); + + return find_name_on_subnet(wins_server_subnet, question, FIND_ANY_NAME); +} + +#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; + unstring qname; + + debuglevel_set(-1); + + while (1) { + NTSTATUS status; + + status = read_data_ntstatus(fd_in, (char *)&r, sizeof(r)); + + if (!NT_STATUS_IS_OK(status)) { + break; + } + + pull_ascii_nstring( qname, sizeof(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(struct messaging_context *msg) +{ + int fd1[2], fd2[2]; + NTSTATUS status; + + CatchChild(); + + if (pipe(fd1) || pipe(fd2)) { + DBG_ERR("can't create asyncdns pipes\n"); + return; + } + + child_pid = fork(); + + if (child_pid) { + fd_in = fd1[0]; + fd_out = fd2[1]; + close(fd1[1]); + close(fd2[0]); + DBG_NOTICE("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, sig_term); + + status = reinit_after_fork(msg, nmbd_event_context(), true); + + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("reinit_after_fork() failed\n"); + smb_panic("reinit_after_fork() failed"); + } + + 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(struct messaging_context *msg) +{ + struct query_record r; + struct packet_struct *p, *p2; + struct name_record *namerec; + NTSTATUS status; + + if (fd_in == -1) + return; + + if (!process_exists_by_pid(child_pid)) { + close(fd_in); + close(fd_out); + start_async_dns(msg); + } + + status = read_data_ntstatus(fd_in, (char *)&r, sizeof(r)); + + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("read from child failed: %s\n", nt_errstr(status)); + fd_in = -1; + return; + } + + namerec = add_dns_result(&r.name, r.result); + + if (dns_current) { + if (query_current(&r)) { + DBG_INFO("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)) { + DBG_INFO("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; + + p2 = p->next; + DLIST_REMOVE(dns_queue, p); + free_packet(p); + p = p2; + } else { + p = p->next; + } + } + + if (dns_queue) { + dns_current = dns_queue; + DLIST_REMOVE(dns_queue, dns_queue); + + if (!write_child(dns_current)) { + DBG_NOTICE("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) +{ + if (in_dns || fd_in == -1) + return False; + + if (!dns_current) { + if (!write_child(p)) { + DBG_NOTICE("failed to send DNS query to child!\n"); + return False; + } + dns_current = p; + p->locked = True; + } else { + p->locked = True; + DLIST_ADD(dns_queue, p); + } + + DBG_NOTICE("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 *namerec = NULL; + struct in_addr dns_ip; + unstring qname; + + pull_ascii_nstring(qname, sizeof(qname), question->name); + + DBG_NOTICE("DNS search for %s -\n", nmb_namestr(question)); + + dns_ip.s_addr = interpret_addr(qname); + + namerec = add_dns_result(question, dns_ip); + if(namerec == NULL) { + send_wins_name_query_response(NAM_ERR, p, NULL); + } else { + send_wins_name_query_response(0, p, namerec); + } + return False; +} + +/*************************************************************************** + With sync dns there is no child to kill on SIGTERM. + ****************************************************************************/ + +void kill_async_dns_child(void) +{ + return; +} +#endif diff --git a/source3/nmbd/nmbd.c b/source3/nmbd/nmbd.c new file mode 100644 index 0000000..4bdf4b2 --- /dev/null +++ b/source3/nmbd/nmbd.c @@ -0,0 +1,1091 @@ +/* + 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,2003 (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 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/filesys.h" +#include "lib/cmdline/cmdline.h" +#include "nmbd/nmbd.h" +#include "serverid.h" +#include "messages.h" +#include "../lib/util/pidfile.h" +#include "util_cluster.h" +#include "lib/gencache.h" +#include "lib/global_contexts.h" +#include "source3/lib/substitute.h" + +int ClientNMB = -1; +int ClientDGRAM = -1; +int global_nmb_port = -1; + +extern bool rescan_listen_set; +extern bool global_in_nmbd; + +/* have we found LanMan clients yet? */ +bool found_lm_clients = False; + +/* what server type are we currently */ + +time_t StartupTime = 0; + +struct tevent_context *nmbd_event_context(void) +{ + return global_event_context(); +} + +/**************************************************************************** ** + Handle a SIGTERM in band. + **************************************************************************** */ + +static void terminate(struct messaging_context *msg) +{ + DBG_WARNING("Got SIGTERM: going down...\n"); + + /* Write out wins.dat file if samba is a WINS server */ + wins_write_database(0,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(); + + pidfile_unlink(lp_pid_directory(), "nmbd"); + + exit(0); +} + +static void nmbd_sig_term_handler(struct tevent_context *ev, + struct tevent_signal *se, + int signum, + int count, + void *siginfo, + void *private_data) +{ + struct messaging_context *msg = talloc_get_type_abort( + private_data, struct messaging_context); + + terminate(msg); +} + +/* + handle stdin becoming readable when we are in --foreground mode + */ +static void nmbd_stdin_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, + void *private_data) +{ + char c; + if (read(0, &c, 1) != 1) { + struct messaging_context *msg = talloc_get_type_abort( + private_data, struct messaging_context); + + DBG_WARNING("EOF on stdin\n"); + terminate(msg); + } +} + +static bool nmbd_setup_sig_term_handler(struct messaging_context *msg) +{ + struct tevent_signal *se; + + se = tevent_add_signal(nmbd_event_context(), + nmbd_event_context(), + SIGTERM, 0, + nmbd_sig_term_handler, + msg); + if (!se) { + DBG_ERR("failed to setup SIGTERM handler\n"); + return false; + } + + return true; +} + +static bool nmbd_setup_stdin_handler(struct messaging_context *msg, bool foreground) +{ + if (foreground) { + /* if we are running in the foreground then look for + EOF on stdin, and exit if it happens. This allows + us to die if the parent process dies + Only do this on a pipe or socket, no other device. + */ + struct stat st; + if (fstat(0, &st) != 0) { + return false; + } + if (S_ISFIFO(st.st_mode) || S_ISSOCK(st.st_mode)) { + tevent_add_fd(nmbd_event_context(), + nmbd_event_context(), + 0, + TEVENT_FD_READ, + nmbd_stdin_handler, + msg); + } + } + + return true; +} + +static void msg_reload_nmbd_services(struct messaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data); + +static void nmbd_sig_hup_handler(struct tevent_context *ev, + struct tevent_signal *se, + int signum, + int count, + void *siginfo, + void *private_data) +{ + struct messaging_context *msg = talloc_get_type_abort( + private_data, struct messaging_context); + + DBG_WARNING("Got SIGHUP dumping debug info.\n"); + msg_reload_nmbd_services(msg, NULL, MSG_SMB_CONF_UPDATED, + messaging_server_id(msg), NULL); +} + +static bool nmbd_setup_sig_hup_handler(struct messaging_context *msg) +{ + struct tevent_signal *se; + + se = tevent_add_signal(nmbd_event_context(), + nmbd_event_context(), + SIGHUP, 0, + nmbd_sig_hup_handler, + msg); + if (!se) { + DBG_ERR("failed to setup SIGHUP handler\n"); + return false; + } + + return true; +} + +/**************************************************************************** ** + Handle a SHUTDOWN message from smbcontrol. + **************************************************************************** */ + +static void nmbd_terminate(struct messaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + terminate(msg); +} + +/**************************************************************************** ** + 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. + Doesn't return until a network interface is up. + ************************************************************************** */ + +static void reload_interfaces(time_t t) +{ + static time_t lastt; + int n; + bool print_waiting_msg = true; + struct subnet_record *subrec; + + if (t && ((t - lastt) < NMBD_INTERFACES_RELOAD)) { + return; + } + + lastt = t; + + if (!interfaces_changed()) { + return; + } + + try_again: + + /* 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--) { + char str[INET6_ADDRSTRLEN]; + const struct interface *iface = get_interface(n); + struct in_addr ip, nmask; + + if (!iface) { + DBG_WARNING("reload_interfaces: failed to get interface %d\n", n); + continue; + } + + /* Ensure we're only dealing with IPv4 here. */ + if (iface->ip.ss_family != AF_INET) { + DBG_NOTICE("reload_interfaces: " + "ignoring non IPv4 interface.\n"); + continue; + } + + ip = ((const struct sockaddr_in *)(const void *)&iface->ip)->sin_addr; + nmask = ((const struct sockaddr_in *)(const void *) + &iface->netmask)->sin_addr; + + /* + * 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 (is_loopback_addr((const struct sockaddr *)(const void *)&iface->ip)) { + DBG_NOTICE("reload_interfaces: Ignoring loopback " + "interface %s\n", + print_sockaddr(str, sizeof(str), &iface->ip) ); + continue; + } + + for (subrec=subnetlist; subrec; subrec=subrec->next) { + if (ip_equal_v4(ip, subrec->myip) && + ip_equal_v4(nmask, subrec->mask_ip)) { + break; + } + } + + if (!subrec) { + /* it wasn't found! add it */ + DBG_NOTICE("Found new interface %s\n", + print_sockaddr(str, + sizeof(str), &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); + struct in_addr ip, nmask; + if (!iface) { + continue; + } + /* Ensure we're only dealing with IPv4 here. */ + if (iface->ip.ss_family != AF_INET) { + DBG_NOTICE("reload_interfaces: " + "ignoring non IPv4 interface.\n"); + continue; + } + ip = ((struct sockaddr_in *)(void *) + &iface->ip)->sin_addr; + nmask = ((struct sockaddr_in *)(void *) + &iface->netmask)->sin_addr; + if (ip_equal_v4(ip, subrec->myip) && + ip_equal_v4(nmask, subrec->mask_ip)) { + break; + } + } + if (n == -1) { + /* oops, an interface has disappeared. 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 */ + DBG_NOTICE("Deleting dead interface %s\n", + inet_ntoa(subrec->myip)); + close_subnet(subrec); + } + } + + rescan_listen_set = True; + + /* We need to wait if there are no subnets... */ + if (FIRST_SUBNET == NULL) { + void (*saved_handler)(int); + + if (print_waiting_msg) { + DBG_WARNING("reload_interfaces: " + "No subnets to listen to. Waiting..\n"); + print_waiting_msg = false; + } + + /* + * Whilst we're waiting for an interface, allow SIGTERM to + * cause us to exit. + */ + saved_handler = CatchSignal(SIGTERM, SIG_DFL); + + /* We only count IPv4, non-loopback interfaces here. */ + while (iface_count_v4_nl() == 0) { + usleep(NMBD_WAIT_INTERFACES_TIME_USEC); + load_interfaces(); + } + + CatchSignal(SIGTERM, saved_handler); + + /* + * We got an interface, go back to blocking term. + */ + + goto try_again; + } +} + +/**************************************************************************** ** + Reload the services file. + **************************************************************************** */ + +static bool reload_nmbd_services(bool test) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + bool ret; + + set_remote_machine_name("nmbd", False); + + if ( lp_loaded() ) { + char *fname = lp_next_configfile(talloc_tos(), lp_sub); + if (file_exist(fname) && !strcsequal(fname,get_dyn_CONFIGFILE())) { + set_dyn_CONFIGFILE(fname); + test = False; + } + TALLOC_FREE(fname); + } + + if ( test && !lp_file_list_changed() ) + return(True); + + ret = lp_load_global(get_dyn_CONFIGFILE()); + + /* perhaps the config filename is now set */ + if ( !test ) { + DBG_NOTICE( "services not loaded\n" ); + reload_nmbd_services( True ); + } + + reopen_logs(); + + return(ret); +} + +/**************************************************************************** ** + * React on 'smbcontrol nmbd reload-config' in the same way as to SIGHUP + **************************************************************************** */ + +static void msg_reload_nmbd_services(struct messaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + write_browse_list( 0, True ); + dump_all_namelists(); + reload_nmbd_services( True ); + reopen_logs(); + reload_interfaces(0); + nmbd_init_my_netbios_names(); +} + +static void msg_nmbd_send_packet(struct messaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id src, + DATA_BLOB *data) +{ + struct packet_struct *p = (struct packet_struct *)data->data; + struct subnet_record *subrec; + struct sockaddr_storage ss; + const struct sockaddr_storage *pss; + const struct in_addr *local_ip; + + DBG_DEBUG("Received send_packet from %u\n", (unsigned int)procid_to_pid(&src)); + + if (data->length != sizeof(struct packet_struct)) { + DBG_WARNING("Discarding invalid packet length from %u\n", + (unsigned int)procid_to_pid(&src)); + return; + } + + if ((p->packet_type != NMB_PACKET) && + (p->packet_type != DGRAM_PACKET)) { + DBG_WARNING("Discarding invalid packet type from %u: %d\n", + (unsigned int)procid_to_pid(&src), p->packet_type); + return; + } + + in_addr_to_sockaddr_storage(&ss, p->ip); + pss = iface_ip((struct sockaddr *)(void *)&ss); + + if (pss == NULL) { + DBG_WARNING("Could not find ip for packet from %u\n", + (unsigned int)procid_to_pid(&src)); + return; + } + + local_ip = &((const struct sockaddr_in *)pss)->sin_addr; + subrec = FIRST_SUBNET; + + p->recv_fd = -1; + p->send_fd = (p->packet_type == NMB_PACKET) ? + subrec->nmb_sock : subrec->dgram_sock; + + for (subrec = FIRST_SUBNET; subrec != NULL; + subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) { + if (ip_equal_v4(*local_ip, subrec->myip)) { + p->send_fd = (p->packet_type == NMB_PACKET) ? + subrec->nmb_sock : subrec->dgram_sock; + break; + } + } + + if (p->packet_type == DGRAM_PACKET) { + p->port = 138; + p->packet.dgram.header.source_ip.s_addr = local_ip->s_addr; + p->packet.dgram.header.source_port = 138; + } + + send_packet(p); +} + +/**************************************************************************** ** + The main select loop. + **************************************************************************** */ + +static void process(struct messaging_context *msg) +{ + bool run_election; + + while( True ) { + time_t t = time(NULL); + TALLOC_CTX *frame = talloc_stackframe(); + + /* + * 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(msg, run_election)) { + TALLOC_FREE(frame); + return; + } + + /* + * Process all incoming packets + * read above. This calls the success and + * failure functions registered when response + * packets arrive, 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 synchronising the domain master + * browser server lists with ourselves as a local + * master browser. + * (nmbd_sendannounce.c) + */ + + announce_myself_to_domain_master_browser(t); + + /* + * Fulfill any remote announce requests. + * (nmbd_sendannounce.c) + */ + + announce_remote(t); + + /* + * Fulfill 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 those 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); + + /* check for new network interfaces */ + + reload_interfaces(t); + + /* free up temp memory */ + TALLOC_FREE(frame); + } +} + +/**************************************************************************** ** + Open the socket communication. + **************************************************************************** */ + +static bool open_sockets(bool isdaemon, int port) +{ + struct sockaddr_storage ss; + const char *sock_addr = lp_nbt_client_socket_address(); + + /* + * 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 (!interpret_string_addr(&ss, sock_addr, + AI_NUMERICHOST|AI_PASSIVE)) { + DBG_ERR("open_sockets: unable to get socket address " + "from string %s\n", sock_addr); + return false; + } + if (ss.ss_family != AF_INET) { + DBG_ERR("open_sockets: unable to use IPv6 socket" + "%s in nmbd\n", + sock_addr); + return false; + } + + if (isdaemon) { + ClientNMB = open_socket_in(SOCK_DGRAM, &ss, port, true); + } else { + ClientNMB = 0; + } + + if (ClientNMB < 0) { + return false; + } + + ClientDGRAM = open_socket_in(SOCK_DGRAM, &ss, DGRAM_PORT, true); + + if (ClientDGRAM < 0) { + if (ClientNMB != 0) { + close(ClientNMB); + } + return false; + } + + /* we are never interested in SIGPIPE */ + BlockSignals(True,SIGPIPE); + + set_socket_options( ClientNMB, "SO_BROADCAST" ); + set_socket_options( ClientDGRAM, "SO_BROADCAST" ); + + /* Ensure we're non-blocking. */ + set_blocking( ClientNMB, False); + set_blocking( ClientDGRAM, False); + + DBG_INFO( "open_sockets: Broadcast sockets opened.\n" ); + return( True ); +} + +/**************************************************************************** ** + main program + **************************************************************************** */ + + int main(int argc, const char *argv[]) +{ + struct samba_cmdline_daemon_cfg *cmdline_daemon_cfg = NULL; + bool log_stdout = false; + poptContext pc; + char *p_lmhosts = NULL; + int opt; + struct messaging_context *msg; + struct poptOption long_options[] = { + POPT_AUTOHELP + { + .longName = "hosts", + .shortName = 'H', + .argInfo = POPT_ARG_STRING, + .arg = &p_lmhosts, + .val = 0, + .descrip = "Load a netbios hosts file", + }, + { + .longName = "port", + .shortName = 'p', + .argInfo = POPT_ARG_INT, + .arg = &global_nmb_port, + .val = 0, + .descrip = "Listen on the specified port", + }, + POPT_COMMON_SAMBA + POPT_COMMON_DAEMON + POPT_COMMON_VERSION + POPT_TABLEEND + }; + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + TALLOC_CTX *frame; + NTSTATUS status; + bool ok; + + /* + * Do this before any other talloc operation + */ + talloc_enable_null_tracking(); + frame = talloc_stackframe(); + + /* + * We want total control over the permissions on created files, + * so set our umask to 0. + */ + umask(0); + + smb_init_locale(); + + ok = samba_cmdline_init(frame, + SAMBA_CMDLINE_CONFIG_SERVER, + true /* require_smbconf */); + if (!ok) { + DBG_ERR("Failed to init cmdline parser!\n"); + TALLOC_FREE(frame); + exit(ENOMEM); + } + + cmdline_daemon_cfg = samba_cmdline_get_daemon_cfg(); + + global_nmb_port = NMB_PORT; + + pc = samba_popt_get_context(getprogname(), + argc, + argv, + long_options, + 0); + if (pc == NULL) { + DBG_ERR("Failed to setup popt context!\n"); + TALLOC_FREE(frame); + exit(1); + } + + while ((opt = poptGetNextOpt(pc)) != -1) { + d_fprintf(stderr, "\nInvalid options\n\n"); + poptPrintUsage(pc, stderr, 0); + exit(1); + }; + poptFreeContext(pc); + + global_in_nmbd = true; + + StartupTime = time(NULL); + + sys_srandom(time(NULL) ^ getpid()); + + if (is_default_dyn_LOGFILEBASE()) { + char *lfile = NULL; + if (asprintf(&lfile, "%s/log.nmbd", get_dyn_LOGFILEBASE()) < 0) { + exit(1); + } + lp_set_logfile(lfile); + SAFE_FREE(lfile); + } + + dump_core_setup("nmbd", lp_logfile(talloc_tos(), lp_sub)); + + /* POSIX demands that signals are inherited. If the invoking process has + * these signals masked, we will have problems, as we won't receive them. */ + BlockSignals(False, SIGHUP); + BlockSignals(False, SIGUSR1); + BlockSignals(False, SIGTERM); + +#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 + + /* Ignore children - no zombies. */ + CatchChild(); + + log_stdout = (debug_get_log_type() == DEBUG_STDOUT); + if ( cmdline_daemon_cfg->interactive ) { + log_stdout = True; + } + + if ( log_stdout && cmdline_daemon_cfg->fork ) { + DBG_ERR("ERROR: Can't log to stdout (-S) unless daemon is in foreground (-F) or interactive (-i)\n"); + exit(1); + } + + reopen_logs(); + + DBG_STARTUP_NOTICE("nmbd version %s started.\n%s\n", + samba_version_string(), + samba_copyright_string()); + + if (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC + && !lp_parm_bool(-1, "server role check", "inhibit", false)) { + /* TODO: when we have a merged set of defaults for + * loadparm, we could possibly check if the internal + * nbt server is in the list, and allow a startup if disabled */ + DBG_ERR("server role = 'active directory domain controller' not compatible with running nmbd standalone.\n" + "You should start 'samba' instead, and it will control starting the internal nbt server\n"); + exit(1); + } + + if (!cluster_probe_ok()) { + exit(1); + } + + msg = messaging_init(NULL, global_event_context()); + if (msg == NULL) { + DBG_ERR("Failed to init messaging context!\n"); + return 1; + } + + if ( !reload_nmbd_services(False) ) + return(-1); + + if (!nmbd_init_my_netbios_names()) { + return -1; + } + + reload_nmbd_services( True ); + + if (strequal(lp_workgroup(),"*")) { + DBG_ERR("ERROR: a workgroup name of * is no longer supported\n"); + exit(1); + } + + set_samba_nb_type(); + + if (!cmdline_daemon_cfg->daemon && !is_a_socket(0)) { + DBG_NOTICE("standard input is not a socket, assuming -D option\n"); + cmdline_daemon_cfg->daemon = true; + } + + if (cmdline_daemon_cfg->daemon && !cmdline_daemon_cfg->interactive) { + DBG_NOTICE("Becoming a daemon.\n"); + become_daemon(cmdline_daemon_cfg->fork, + cmdline_daemon_cfg->no_process_group, + log_stdout); + } else if (!cmdline_daemon_cfg->interactive) { + daemon_status("nmbd", "Starting process..."); + } + +#ifdef HAVE_SETPGID + /* + * If we're interactive we want to set our own process group for + * signal management. + */ + if (cmdline_daemon_cfg->interactive && + !cmdline_daemon_cfg->no_process_group) + { + 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_wins_dns_proxy()) { + start_async_dns(msg); + } +#endif + + ok = directory_create_or_exist(lp_lock_directory(), 0755); + if (!ok) { + exit_daemon("Failed to create directory for lock files, check 'lock directory'", errno); + } + + ok = directory_create_or_exist(lp_pid_directory(), 0755); + if (!ok) { + exit_daemon("Failed to create directory for pid files, check 'pid directory'", errno); + } + + pidfile_create(lp_pid_directory(), "nmbd"); + + status = reinit_after_fork(msg, nmbd_event_context(), false); + + if (!NT_STATUS_IS_OK(status)) { + exit_daemon("reinit_after_fork() failed", map_errno_from_nt_status(status)); + } + + /* + * Do not initialize the parent-child-pipe before becoming + * a daemon: this is used to detect a died parent in the child + * process. + */ + status = init_before_fork(); + if (!NT_STATUS_IS_OK(status)) { + exit_daemon(nt_errstr(status), map_errno_from_nt_status(status)); + } + + if (!nmbd_setup_sig_term_handler(msg)) + exit_daemon("NMBD failed to setup signal handler", EINVAL); + if (!nmbd_setup_stdin_handler(msg, !cmdline_daemon_cfg->fork)) + exit_daemon("NMBD failed to setup stdin handler", EINVAL); + if (!nmbd_setup_sig_hup_handler(msg)) + exit_daemon("NMBD failed to setup SIGHUP handler", EINVAL); + + if (!messaging_parent_dgm_cleanup_init(msg)) { + exit(1); + } + + messaging_register(msg, NULL, MSG_FORCE_ELECTION, + nmbd_message_election); +#if 0 + /* Until winsrepl is done. */ + messaging_register(msg, NULL, MSG_WINS_NEW_ENTRY, + nmbd_wins_new_entry); +#endif + messaging_register(msg, NULL, MSG_SHUTDOWN, + nmbd_terminate); + messaging_register(msg, NULL, MSG_SMB_CONF_UPDATED, + msg_reload_nmbd_services); + messaging_register(msg, NULL, MSG_SEND_PACKET, + msg_nmbd_send_packet); + + TimeInit(); + + DBG_NOTICE("Opening sockets %d\n", global_nmb_port); + + if ( !open_sockets( cmdline_daemon_cfg->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() ) { + kill_async_dns_child(); + exit_daemon("NMBD failed when creating subnet lists", EACCES); + } + + /* Load in any static local names. */ + if (p_lmhosts) { + set_dyn_LMHOSTSFILE(p_lmhosts); + } + load_lmhosts_file(get_dyn_LMHOSTSFILE()); + DBG_NOTICE("Loaded hosts file %s\n", get_dyn_LMHOSTSFILE()); + + /* If we are acting as a WINS server, initialise data structures. */ + if( !initialise_wins() ) { + kill_async_dns_child(); + exit_daemon( "NMBD failed when initialising WINS server.", EACCES); + } + + /* + * 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() ) { + kill_async_dns_child(); + exit_daemon( "NMBD failed when creating my workgroup.", EACCES); + } + + if (!initialize_nmbd_proxy_logon()) { + kill_async_dns_child(); + exit_daemon( "NMBD failed to setup nmbd_proxy_logon.", EACCES); + } + + if (!nmbd_init_packet_server()) { + kill_async_dns_child(); + exit_daemon( "NMBD failed to setup packet server.", EACCES); + } + + if (!cmdline_daemon_cfg->interactive) { + daemon_ready("nmbd"); + } + + TALLOC_FREE(frame); + process(msg); + + kill_async_dns_child(); + return(0); +} diff --git a/source3/nmbd/nmbd.h b/source3/nmbd/nmbd.h new file mode 100644 index 0000000..f207eb9 --- /dev/null +++ b/source3/nmbd/nmbd.h @@ -0,0 +1,39 @@ +/* + * Unix SMB/CIFS implementation. + * NBT netbios routines and daemon - version 2 + * + * Copyright (C) Guenther Deschner 2011 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _NMBD_NMBD_H_ +#define _NMBD_NMBD_H_ + +#ifndef HAVE_PIPE +#define SYNC_DNS 1 +#endif + +#include "libsmb/nmblib.h" +#include "nmbd/nmbd_proto.h" + +#define NMBD_WAIT_INTERFACES_TIME_USEC (250 * 1000) + +/**************************************************************************** +true if two IPv4 addresses are equal +****************************************************************************/ + +#define ip_equal_v4(ip1,ip2) ((ip1).s_addr == (ip2).s_addr) + +#endif /* _NMBD_NMBD_H_ */ diff --git a/source3/nmbd/nmbd_become_dmb.c b/source3/nmbd/nmbd_become_dmb.c new file mode 100644 index 0000000..d006a6f --- /dev/null +++ b/source3/nmbd/nmbd_become_dmb.c @@ -0,0 +1,398 @@ +/* + 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-2003 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "../librpc/gen_ndr/svcctl.h" +#include "nmbd/nmbd.h" + +extern uint16_t 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) +{ + unstring failname; + struct work_record *work; + struct server_record *servrec; + + pull_ascii_nstring(failname, sizeof(failname), fail_name->name); + work = find_workgroup_on_subnet(subrec, failname); + if(!work) { + DEBUG(0,("become_domain_master_fail: Error - cannot find \ +workgroup %s on subnet %s\n", failname, 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_t nb_flags, + int ttl, struct in_addr registered_ip) +{ + unstring regname; + struct work_record *work; + struct server_record *servrec; + + pull_ascii_nstring(regname, sizeof(regname), registered_name->name); + work = find_workgroup_on_subnet( subrec, regname); + + if(!work) { + DEBUG(0,("become_domain_master_stage2: Error - cannot find \ +workgroup %s on subnet %s\n", regname, 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(), regname, 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; + const struct in_addr *nip; + + /* 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 IPv4 address as the domain master + * browser ip. */ + nip = first_ipv4_iface(); + if (!nip) { + DEBUG(0,("become_domain_master_stage2: " + "Error. get_interface returned NULL\n")); + return; + } + my_first_ip = *nip; + + 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, const 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) +{ + unstring name; + struct in_addr allones_ip; + + pull_ascii_nstring(name, sizeof(name), nmbname->name); + + /* If the given ip is not ours, then we can't become a domain + controller 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. */ + + allones_ip.s_addr = htonl(INADDR_BROADCAST); + + if(ismyip_v4(ip) || ip_equal_v4(allones_ip, ip) || is_zero_ip_v4(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, 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), 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) +{ + unstring name; + + /* 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. */ + pull_ascii_nstring(name, sizeof(name), question_name->name); + become_domain_master_stage1(subrec, 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, workgroup_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 browser 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, workgroup_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 (IS_DC) + add_logon_names(); + + /* Do the domain master names. */ + if(lp_domain_master()) { + 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/source3/nmbd/nmbd_become_lmb.c b/source3/nmbd/nmbd_become_lmb.c new file mode 100644 index 0000000..0761f1a --- /dev/null +++ b/source3/nmbd/nmbd_become_lmb.c @@ -0,0 +1,587 @@ +/* + 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-2003 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "nmbd/nmbd.h" +#include "../librpc/gen_ndr/svcctl.h" +#include "lib/util/string_wrappers.h" + +extern uint16_t 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_t nb_type ) +{ + unstring name; + struct name_record *namerec; + + if((namerec = find_name_on_subnet(unicast_subnet, nmbname, FIND_SELF_NAME)) == NULL) { + pull_ascii_nstring(name, sizeof(name), nmbname->name); + /* The name needs to be created on the unicast subnet. */ + (void)add_name_to_subnet( unicast_subnet, 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, const 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) { + DBG_ERR("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) { + DBG_ERR("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; + unstring relname; + + memcpy((char *)&force_new_election, userdata->data, sizeof(bool)); + + DBG_NOTICE("unbecome_local_master_success: released name %s.\n", + nmb_namestr(released_name)); + + /* Now reset the workgroup and server state. */ + pull_ascii_nstring(relname, sizeof(relname), released_name->name); + reset_workgroup_state( subrec, relname, force_new_election ); + + DBG_WARNING("*****\n\n" + "Samba name server %s has stopped being a local master browser " + "for workgroup %s on subnet %s\n\n*****\n", + lp_netbios_name(), relname, 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; + unstring failname; + + memcpy((char *)&force_new_election, userdata->data, sizeof(bool)); + + DBG_WARNING("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. */ + pull_ascii_nstring(failname, sizeof(failname), fail_name->name); + reset_workgroup_state( subrec, failname, force_new_election ); + + DBG_WARNING("*****\n\n" + "Samba name server %s has stopped being a local master browser " + "for workgroup %s on subnet %s\n\n*****\n", + lp_netbios_name(), failname, subrec->subnet_name); +} + +/******************************************************************* + Utility function to remove the WORKGROUP<1d> name. +******************************************************************/ + +static void release_1d_name( struct subnet_record *subrec, const 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; + size_t size = sizeof(struct userdata_struct) + sizeof(bool); + + if((userdata = (struct userdata_struct *)SMB_MALLOC(size)) == NULL) { + DBG_ERR("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) +{ + DBG_INFO("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; + + DBG_INFO("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. */ + + DBG_NOTICE("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) { + DBG_WARNING("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_t nb_flags, + int ttl, struct in_addr registered_ip) +{ + int i = 0; + struct server_record *sl; + struct work_record *work; + struct server_record *servrec; + unstring regname; + + pull_ascii_nstring(regname, sizeof(regname), registered_name->name); + work = find_workgroup_on_subnet( subrec, regname); + + if(!work) { + DBG_WARNING("become_local_master_stage2: Error - cannot find \ +workgroup %s on subnet %s\n", regname, subrec->subnet_name); + return; + } + + if((servrec = find_server_in_workgroup( work, lp_netbios_name())) == NULL) { + DBG_WARNING("become_local_master_stage2: Error - cannot find server %s \ +in workgroup %s on subnet %s\n", + lp_netbios_name(), regname, subrec->subnet_name); + work->mst_state = lp_local_master() ? MST_POTENTIAL : MST_NONE; + return; + } + + DBG_NOTICE("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(); + + DBG_WARNING( "*****\n\n" + "Samba name server %s is now a local master browser " + "for workgroup %s on subnet %s\n\n*****\n", + lp_netbios_name(), work->work_group, 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) +{ + unstring failname; + struct work_record *work; + + DBG_WARNING("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); + + pull_ascii_nstring(failname, sizeof(failname), fail_name->name); + work = find_workgroup_on_subnet( subrec, failname); + + if(!work) { + DBG_WARNING("become_local_master_fail2: Error - cannot find \ +workgroup %s on subnet %s\n", failname, 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_t 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) { + DBG_WARNING("become_local_master_stage1: Error - cannot find \ +%s on subnet %s\n", work_name, subrec->subnet_name); + return; + } + + DBG_NOTICE("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) { + DBG_WARNING("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) { + DBG_WARNING("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 ); + + DBG_WARNING("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; + size_t size = sizeof(struct userdata_struct) + sizeof(fstring) + 1; + + /* Sanity check. */ + if (!lp_local_master()) { + DBG_WARNING("become_local_master_browser: Samba not configured as a local master browser.\n"); + return; + } + + if(!AM_POTENTIAL_MASTER_BROWSER(work)) { + DBG_NOTICE("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) { + DBG_WARNING("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; + } + + DBG_NOTICE("become_local_master_browser: Starting to become a master browser for workgroup \ +%s on subnet %s\n", work->work_group, subrec->subnet_name); + + DBG_NOTICE("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 *)SMB_MALLOC(size)) == NULL) { + DBG_ERR("become_local_master_browser: malloc fail.\n"); + return; + } + + userdata->copy_fn = NULL; + userdata->free_fn = NULL; + userdata->userdata_len = strlen(work->work_group)+1; + strlcpy(userdata->data, work->work_group, size - sizeof(*userdata)); + + /* 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) +{ + DBG_INFO("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 + + unstrcpy(work->local_master_browser_name, newname); +} diff --git a/source3/nmbd/nmbd_browserdb.c b/source3/nmbd/nmbd_browserdb.c new file mode 100644 index 0000000..b5fdbab --- /dev/null +++ b/source3/nmbd/nmbd_browserdb.c @@ -0,0 +1,180 @@ +/* + 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 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ +/* -------------------------------------------------------------------------- ** + * 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" +#include "nmbd/nmbd.h" +#include "lib/util/string_wrappers.h" + +/* -------------------------------------------------------------------------- ** + * Variables... + * + * lmb_browserlist - This is our local master browser list. + */ + +struct browse_cache_record *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 ) +{ + DLIST_REMOVE(lmb_browserlist, browc); + SAFE_FREE(browc); +} + +/* ************************************************************************** ** + * 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 ); +} + +/* ************************************************************************** ** + * 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( const char *work_name, + const char *browser_name, + struct in_addr ip ) +{ + struct browse_cache_record *browc; + time_t now = time( NULL ); + + browc = SMB_MALLOC_P(struct browse_cache_record); + + 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 ); + + unstrcpy( browc->lmb_name, browser_name); + unstrcpy( browc->work_group, work_name); + if (!strupper_m( browc->lmb_name )) { + SAFE_FREE(browc); + return NULL; + } + if (!strupper_m( browc->work_group )) { + SAFE_FREE(browc); + return NULL; + } + + browc->ip = ip; + + DLIST_ADD_END(lmb_browserlist, browc); + + DEBUG(3, ("nmbd_browserdb:create_browser_in_lmb_cache()\n")); + DEBUGADD(3, (" Added lmb cache entry for workgroup %s name %s IP %s " + "ttl %d\n", browc->work_group, browc->lmb_name, + inet_ntoa(ip), (int)browc->death_time)); + + return( browc ); +} + +/* ************************************************************************** ** + * 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( const char *browser_name ) +{ + struct browse_cache_record *browc; + + for( browc = lmb_browserlist; browc; browc = browc->next ) { + if( strequal( browser_name, browc->lmb_name ) ) { + break; + } + } + + return browc; +} + +/* ************************************************************************** ** + * 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 = lmb_browserlist; browc; browc = nextbrowc) { + nextbrowc = browc->next; + + if( browc->death_time < t ) { + DEBUG(3, ("nmbd_browserdb:expire_lmb_browsers()\n")); + DEBUGADD(3, (" Removing timed out lmb entry %s\n", + browc->lmb_name)); + remove_lmb_browser_entry( browc ); + } + } +} diff --git a/source3/nmbd/nmbd_browsesync.c b/source3/nmbd/nmbd_browsesync.c new file mode 100644 index 0000000..68b8b95 --- /dev/null +++ b/source3/nmbd/nmbd_browsesync.c @@ -0,0 +1,692 @@ +/* + 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-2003 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "nmbd/nmbd.h" +#include "lib/util/string_wrappers.h" + +/* This is our local master browser list database. */ +extern struct browse_cache_record *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 = lmb_browserlist; browc; browc = browc->next ) { + 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) +{ + char outbuf[1024]; + unstring myname; + unstring dmb_name; + char *p; + + if(ismyip_v4(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++; + + unstrcpy(myname, lp_netbios_name()); + if (!strupper_m(myname)) { + DEBUG(2,("strupper_m %s failed\n", myname)); + return; + } + myname[15]='\0'; + /* The call below does CH_UNIX -> CH_DOS conversion. JRA */ + push_ascii(p, myname, sizeof(outbuf)-PTR_DIFF(p,outbuf)-1, STR_TERMINATE); + + p = skip_string(outbuf,sizeof(outbuf),p); + + 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 ); + } + + /* Target name for send_mailslot must be in UNIX charset. */ + pull_ascii_nstring(dmb_name, sizeof(dmb_name), work->dmb_name.name); + send_mailslot(True, BROWSE_MAILSLOT, outbuf,PTR_DIFF(p,outbuf), + lp_netbios_name(), 0x0, dmb_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) +{ + unstring dmb_name; + + 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 ); + } + + pull_ascii_nstring(dmb_name, sizeof(dmb_name), work->dmb_name.name); + sync_browse_lists(work, dmb_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->rdlength > 0) { + char *p = answers->rdata; + int numnames = CVAL(p, 0); + + p += 1; + + while (numnames--) { + unstring qname; + uint16_t nb_flags; + int name_type; + + pull_ascii_nstring(qname, sizeof(qname), p); + name_type = CVAL(p,15); + nb_flags = get_nb_flags(&p[16]); + trim_char(qname,'\0',' '); + + 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 ? userdata->data : "NULL" ); + 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 address + * 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; + size_t size = sizeof(struct userdata_struct) + sizeof(fstring)+1; + unstring qname; + + pull_ascii_nstring(qname, sizeof(qname), q_name->name); + if( !(work = find_workgroup_on_subnet(subrec, qname)) ) { + if( DEBUGLVL( 0 ) ) { + dbgtext( "find_domain_master_name_query_success:\n" ); + dbgtext( "Failed to find workgroup %s\n", qname); + } + return; + } + + /* First check if we already have a dmb for this workgroup. */ + + if(!is_zero_ip_v4(work->dmb_addr) && ip_equal_v4(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_v4(&work->dmb_addr); + } + + /* Now initiate the node status request. */ + + /* We used to use the name "*",0x0 here, but some Windows + * servers don't answer that name. However we *know* they + * have the name workgroup#1b (as we just looked it up). + * So do the node status request on this name instead. + * Found at LBL labs. JRA. + */ + + make_nmb_name(&nmbname,work->work_group,0x1b); + + /* 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 *)SMB_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; + strlcpy(userdata->data, work->work_group, size - sizeof(*userdata)); + + 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) +{ + /* 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; + } + + /* First, query for the WORKGROUP<1b> name from the WINS server. */ + query_name(unicast_subnet, work->work_group, 0x1b, + 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) +{ + unstring 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->rdlength > 0) { + char *p = answers->rdata; + int numnames = CVAL(p, 0); + + p += 1; + + while (numnames--) { + unstring qname; + uint16_t nb_flags; + int name_type; + + pull_ascii_nstring(qname, sizeof(qname), p); + name_type = CVAL(p,15); + nb_flags = get_nb_flags(&p[16]); + trim_char(qname,'\0',' '); + + p += 18; + + if(!(nb_flags & NB_GROUP) && (name_type == 0x00) && + server_name[0] == 0) { + /* this is almost certainly the server netbios name */ + strlcpy(server_name, qname, sizeof(server_name)); + continue; + } + + if(!(nb_flags & NB_GROUP) && (name_type == 0x1b)) { + struct work_record *work; + + 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. + */ + + work = find_workgroup_on_subnet( subrec, qname); + if (work == NULL) { + struct nmb_name nmbname; + /* + * Add it - with an hour in the cache. + */ + work = create_workgroup_on_subnet(subrec, qname, 60*60); + if (work == NULL) { + return; + } + + /* remember who the master is */ + strlcpy(work->local_master_browser_name, + server_name, + sizeof(work->local_master_browser_name)); + make_nmb_name(&nmbname, server_name, 0x20); + work->dmb_name = nmbname; + work->dmb_addr = from_ip; + } + break; + } + } + } else if( DEBUGLVL( 1 ) ) { + 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( 2 ) ) { + 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_v4( 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; + + /* 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; + + /* First, query for the *<1b> name from the WINS server. */ + query_name(unicast_subnet, "*", 0x1b, + 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; + size_t 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++; + } + } + + /* leave if we don't have to do any syncs */ + if (count == 0) { + return; + } + + /* sync with a probability of 1/count */ + for (work=unicast_subnet->workgrouplist; work; work = work->next) { + if (strcmp(lp_workgroup(), work->work_group)) { + unstring dmb_name; + + 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); + } + + pull_ascii_nstring(dmb_name, sizeof(dmb_name), work->dmb_name.name); + + DEBUG(3,("Initiating DMB<->DMB sync with %s(%s)\n", + dmb_name, inet_ntoa(work->dmb_addr))); + + sync_browse_lists(work, + dmb_name, + work->dmb_name.name_type, + work->dmb_addr, False, False); + } + } +} diff --git a/source3/nmbd/nmbd_elections.c b/source3/nmbd/nmbd_elections.c new file mode 100644 index 0000000..41470ea --- /dev/null +++ b/source3/nmbd/nmbd_elections.c @@ -0,0 +1,395 @@ +/* + 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-2003 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "nmbd/nmbd.h" +#include "lib/util/string_wrappers.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_t criterion, int timeup,const char *server_name) +{ + char outbuf[1024]; + unstring srv_name; + 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; + unstrcpy(srv_name, server_name); + if (!strupper_m(srv_name)) { + DEBUG(2,("strupper_m failed for %s\n", srv_name)); + return; + } + /* The following call does UNIX -> DOS charset conversion. */ + push_ascii(p, srv_name, sizeof(outbuf)-PTR_DIFF(p,outbuf)-1, STR_TERMINATE); + p = skip_string(outbuf,sizeof(outbuf),p); + + 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) +{ + unstring aname; + pull_ascii_nstring(aname, sizeof(aname), answer_name->name); + DEBUG(3,("check_for_master_browser_success: Local master browser for workgroup %s exists at \ +IP %s (just checking).\n", aname, 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) +{ + unstring workgroup_name; + struct work_record *work; + + pull_ascii_nstring(workgroup_name,sizeof(workgroup_name),question_name->name); + + 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 (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; + + 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); + } + } + } + } +} + +/******************************************************************* + Determine if I win an election. +******************************************************************/ + +static bool win_election(struct work_record *work, int version, + uint32_t criterion, int timeup, const char *server_name) +{ + int mytimeup = time(NULL) - StartupTime; + uint32_t 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_m(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, const char *buf) +{ + struct dgram_packet *dgram = &p->packet.dgram; + int version = CVAL(buf,0); + uint32_t criterion = IVAL(buf,1); + int timeup = IVAL(buf,5)/1000; + unstring server_name; + struct work_record *work; + unstring workgroup_name; + + pull_ascii_nstring(server_name, sizeof(server_name), buf+13); + pull_ascii_nstring(workgroup_name, sizeof(workgroup_name), dgram->dest_name.name); + + 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: + return; +} + +/**************************************************************************** + 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) { + if (work->RunningElection) { + 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(struct messaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + 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/source3/nmbd/nmbd_incomingdgrams.c b/source3/nmbd/nmbd_incomingdgrams.c new file mode 100644 index 0000000..3e27ff5 --- /dev/null +++ b/source3/nmbd/nmbd_incomingdgrams.c @@ -0,0 +1,841 @@ +/* + 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 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "../librpc/gen_ndr/svcctl.h" +#include "nmbd/nmbd.h" +#include "lib/util/string_wrappers.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, const char *buf) +{ + struct dgram_packet *dgram = &p->packet.dgram; + int ttl = IVAL(buf,1)/1000; + unstring announce_name; + uint32_t servertype = IVAL(buf,23); + fstring comment; + struct work_record *work; + struct server_record *servrec; + unstring work_name; + unstring source_name; + ZERO_STRUCT(source_name); + ZERO_STRUCT(announce_name); + + pull_ascii_fstring(comment, buf+31); + + pull_ascii_nstring(announce_name, sizeof(announce_name), buf+5); + pull_ascii_nstring(source_name, sizeof(source_name), dgram->source_name.name); + + 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. */ + pull_ascii_nstring(work_name, sizeof(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())) + unstrcpy(work_name,lp_workgroup()); + + /* + * We are being very aggressive 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); + strlcpy(servrec->serv.comment,comment,sizeof(servrec->serv.comment)); + } + } 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: + return; +} + +/******************************************************************* + Process an incoming WORKGROUP announcement packet. +*******************************************************************/ + +void process_workgroup_announce(struct subnet_record *subrec, struct packet_struct *p, const char *buf) +{ + struct dgram_packet *dgram = &p->packet.dgram; + int ttl = IVAL(buf,1)/1000; + unstring workgroup_announce_name; + unstring master_name; + uint32_t servertype = IVAL(buf,23); + struct work_record *work; + unstring source_name; + unstring dest_name; + + pull_ascii_nstring(workgroup_announce_name,sizeof(workgroup_announce_name),buf+5); + pull_ascii_nstring(master_name,sizeof(master_name),buf+31); + pull_ascii_nstring(source_name,sizeof(source_name),dgram->source_name.name); + pull_ascii_nstring(dest_name,sizeof(dest_name),dgram->dest_name.name); + + 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(dest_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: + return; +} + +/******************************************************************* + Process an incoming local master browser announcement packet. +*******************************************************************/ + +void process_local_master_announce(struct subnet_record *subrec, struct packet_struct *p, const char *buf) +{ + struct dgram_packet *dgram = &p->packet.dgram; + int ttl = IVAL(buf,1)/1000; + unstring server_name; + uint32_t servertype = IVAL(buf,23); + fstring comment; + unstring work_name; + struct work_record *work = NULL; + struct server_record *servrec; + unstring source_name; + + pull_ascii_nstring(server_name,sizeof(server_name),buf+5); + pull_ascii_fstring(comment, buf+31); + pull_ascii_nstring(source_name, sizeof(source_name), dgram->source_name.name); + pull_ascii_nstring(work_name, sizeof(work_name), dgram->dest_name.name); + + 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. */ + + 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. */ + if (servrec->serv.type != + (servertype|SV_TYPE_LOCAL_LIST_ONLY)) { + servrec->serv.type = + servertype|SV_TYPE_LOCAL_LIST_ONLY; + subrec->work_changed = true; + } + if (!strequal(servrec->serv.comment,comment)) { + strlcpy(servrec->serv.comment, + comment, + sizeof(servrec->serv.comment)); + subrec->work_changed = true; + } + update_server_ttl(servrec, ttl); + } + + if (!strequal(work->local_master_browser_name, server_name)) { + set_workgroup_local_master_browser_name( work, server_name ); + subrec->work_changed = true; + } + } else { + /* + * This server is announcing it is going down. Remove it from the + * workgroup. + */ + if(!is_myname(server_name) && + ((servrec = find_server_in_workgroup( work, server_name))!=NULL)) { + remove_server_from_workgroup( work, servrec); + } + } + +done: + return; +} + +/******************************************************************* + 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,const char *buf) +{ + unstring local_master_name; + struct work_record *work; + struct browse_cache_record *browrec; + + pull_ascii_nstring(local_master_name,sizeof(local_master_name),buf); + + 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: + return; +} + +/******************************************************************* + Process an incoming LanMan host announcement packet. +*******************************************************************/ + +void process_lm_host_announce(struct subnet_record *subrec, struct packet_struct *p, const char *buf, int len) +{ + struct dgram_packet *dgram = &p->packet.dgram; + uint32_t 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); + unstring announce_name; + struct work_record *work; + struct server_record *servrec; + unstring work_name; + unstring source_name; + fstring comment; + char *s = get_safe_str_ptr(buf,len,discard_const_p(char, buf),9); + + if (!s) { + goto done; + } + s = skip_string(buf,len,s); + if (!s) { + goto done; + } + pull_ascii(comment, s, sizeof(fstring), 43, STR_TERMINATE); + + pull_ascii_nstring(announce_name,sizeof(announce_name),buf+9); + pull_ascii_nstring(source_name,sizeof(source_name),dgram->source_name.name); + /* For a LanMan host announce the workgroup name is the destination name. */ + pull_ascii_nstring(work_name,sizeof(work_name),dgram->dest_name.name); + + DEBUG(3,("process_lm_host_announce: LM Announcement from %s IP %s to \ +%s for server %s.\n", nmb_namestr(&dgram->source_name), 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; + } + + /* + * 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())) + unstrcpy(work_name,lp_workgroup()); + + /* + * We are being very aggressive 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); + strlcpy(servrec->serv.comment,comment,sizeof(servrec->serv.comment)); + } + } 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: + return; +} + +/**************************************************************************** + 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_t token, struct in_addr sendto_ip, + int port) +{ + char outbuf[1024]; + char *p, *countptr; + unsigned int count = 0; + unstring send_to_namestr; +#if 0 + struct server_record *servrec; +#endif + unstring myname; + + 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; + unstrcpy(myname, lp_netbios_name()); + if (!strupper_m(myname)) { + DEBUG(4,("strupper_m %s failed\n", myname)); + return; + } + myname[15]='\0'; + push_ascii(p, myname, sizeof(outbuf)-PTR_DIFF(p,outbuf)-1, STR_TERMINATE); + + p = skip_string(outbuf,sizeof(outbuf),p); + + /* 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 necessarily + * 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; + + strlcpy(p, servrec->serv.name, 16); + strupper_m(p); + count++; + + DEBUG(5,("send_backup_list_response: Adding server %s number %d\n", + p, count)); + + p = skip_string(outbuf,sizeof(outbuf),p); + } +#endif + + SCVAL(countptr, 0, count); + + pull_ascii_nstring(send_to_namestr, sizeof(send_to_namestr), send_to_name->name); + + DEBUG(4,("send_backup_list_response: sending response to %s<00> IP %s with %d servers.\n", + send_to_namestr, inet_ntoa(sendto_ip), count)); + + send_mailslot(True, BROWSE_MAILSLOT, + outbuf,PTR_DIFF(p,outbuf), + lp_netbios_name(), 0, + send_to_namestr,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,const char *buf) +{ + struct dgram_packet *dgram = &p->packet.dgram; + struct work_record *work; + unsigned char max_number_requested = CVAL(buf,0); + uint32_t token = IVAL(buf,1); /* Sender's key index for the workgroup. */ + int name_type = dgram->dest_name.name_type; + unstring workgroup_name; + struct subnet_record *search_subrec = subrec; + + pull_ascii_nstring(workgroup_name, sizeof(workgroup_name), dgram->dest_name.name); + + 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: + return; +} + +/******************************************************************* + 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,const char *buf) +{ + struct dgram_packet *dgram = &p->packet.dgram; + int state = CVAL(buf,0); + struct subnet_record *sr; + + 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")); +} + +/******************************************************************* + 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, const char *buf) +{ + struct dgram_packet *dgram = &p->packet.dgram; + struct work_record *work; + unstring workgroup_name; + + pull_ascii_nstring(workgroup_name, sizeof(workgroup_name), dgram->dest_name.name); + 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: + return; +} + +/******************************************************************* + 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, const char *buf, int len) +{ + struct dgram_packet *dgram = &p->packet.dgram; + unstring workgroup_name; + + pull_ascii_nstring(workgroup_name, sizeof(workgroup_name), dgram->dest_name.name); + 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: + return; +} diff --git a/source3/nmbd/nmbd_incomingrequests.c b/source3/nmbd/nmbd_incomingrequests.c new file mode 100644 index 0000000..06c486d --- /dev/null +++ b/source3/nmbd/nmbd_incomingrequests.c @@ -0,0 +1,590 @@ +/* + 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-2003 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + + 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" +#include "nmbd/nmbd.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; + unstring qname; + bool bcast = nmb->header.nm_flags.bcast; + uint16_t nb_flags = get_nb_flags(nmb->additional->rdata); + bool group = (nb_flags & NB_GROUP) ? True : False; + struct name_record *namerec; + 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_v4(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* !!!!! + */ + + pull_ascii_nstring(qname, sizeof(qname), question->name); + if( !group && !ismyip_v4(owner_ip) && strequal(qname, 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, question, 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_t 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) +{ + unstring name1, name2; + int l1,l2,l3; + + memset(name1, '\0', sizeof(name1)); + memset(name2, '\0', sizeof(name2)); + pull_ascii_nstring(name1, sizeof(name1), n1); + pull_ascii_nstring(name2, sizeof(name2), n2); + n1 = name1; + n2 = name2; + + /* 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,sizeof(name1)); +} + +/**************************************************************************** + 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; + unstring qname; + 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 = NULL; + + pull_ascii_nstring(qname, sizeof(qname), nmb->question.question_name.name); + + 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(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-1] - (18 + 46 + 60); + countptr = buf = rdata; + buf += 1; + buf0 = buf; + + names_added = 0; + + namerec = subrec->namelist; + + while (PTR_DIFF(bufend, buf) > 0) { + if( (namerec->data.source == SELF_NAME) || (namerec->data.source == PERMANENT_NAME) ) { + int name_type = namerec->name.name_type; + unstring name; + + pull_ascii_nstring(name, sizeof(name), namerec->name.name); + if (!strupper_m(name)) { + DEBUG(2,("strupper_m %s failed\n", name)); + return; + } + if (!strequal(name,"*") && + !strequal(name,"__SAMBA__") && + (name_type < 0x1b || name_type >= 0x20 || + ques_type < 0x1b || ques_type >= 0x20 || + strequal(qname, name))) { + /* Start with the name. */ + size_t len; + push_ascii_nstring(buf, name); + len = strlen(buf); + memset(buf + len, ' ', MAX_NETBIOSNAME_LEN - len - 1); + buf[MAX_NETBIOSNAME_LEN - 1] = '\0'; + + /* 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 */ + + names_added++; + } + } + + /* Remove duplicate names. */ + if (names_added > 1) { + /* TODO: should use a real type and + TYPESAFE_QSORT() */ + 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 = namerec->next; + + 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 = 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 check 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_v4(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 requester. 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 *)SMB_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/source3/nmbd/nmbd_lmhosts.c b/source3/nmbd/nmbd_lmhosts.c new file mode 100644 index 0000000..ecf70bd --- /dev/null +++ b/source3/nmbd/nmbd_lmhosts.c @@ -0,0 +1,105 @@ +/* + 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 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + + Revision History: + + Handle lmhosts file reading. + +*/ + +#include "includes.h" +#include "../libcli/nbt/libnbt.h" +#include "nmbd/nmbd.h" + +/**************************************************************************** +Load a lmhosts file. +****************************************************************************/ + +void load_lmhosts_file(const char *fname) +{ + char *name = NULL; + int name_type; + struct sockaddr_storage ss; + TALLOC_CTX *ctx = talloc_init("load_lmhosts_file"); + FILE *fp = startlmhosts( fname ); + + if (!fp) { + DEBUG(2,("load_lmhosts_file: Can't open lmhosts file %s. Error was %s\n", + fname, strerror(errno))); + TALLOC_FREE(ctx); + return; + } + + while (getlmhostsent(ctx, fp, &name, &name_type, &ss) ) { + struct in_addr ipaddr; + struct subnet_record *subrec = NULL; + enum name_source source = LMHOSTS_NAME; + + if (ss.ss_family != AF_INET) { + TALLOC_FREE(name); + continue; + } + + ipaddr = ((struct sockaddr_in *)&ss)->sin_addr; + + /* We find a relevant 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_v4(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_t)NB_ACTIVE,PERMANENT_TTL,source,1,&ipaddr); + (void)add_name_to_subnet(subrec,name,0x20,(uint16_t)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_t)NB_ACTIVE,PERMANENT_TTL,source,1,&ipaddr); + } + } + + TALLOC_FREE(ctx); + 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/source3/nmbd/nmbd_logonnames.c b/source3/nmbd/nmbd_logonnames.c new file mode 100644 index 0000000..cc4776a --- /dev/null +++ b/source3/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-2003 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "../librpc/gen_ndr/svcctl.h" +#include "nmbd/nmbd.h" + +extern uint16_t 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) +{ + unstring failname; + struct work_record *work; + struct server_record *servrec; + + pull_ascii_nstring(failname, sizeof(failname), fail_name->name); + work = find_workgroup_on_subnet(subrec, failname); + if(!work) { + DEBUG(0,("become_logon_server_fail: Error - cannot find \ +workgroup %s on subnet %s\n", failname, 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(), failname, 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_t nb_flags, + int ttl, struct in_addr registered_ip) +{ + unstring reg_name; + struct work_record *work; + struct server_record *servrec; + + pull_ascii_nstring(reg_name, sizeof(reg_name), registered_name->name); + work = find_workgroup_on_subnet( subrec, reg_name); + if(!work) { + DEBUG(0,("become_logon_server_success: Error - cannot find \ +workgroup %s on subnet %s\n", reg_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(), reg_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: Attempting 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/source3/nmbd/nmbd_mynames.c b/source3/nmbd/nmbd_mynames.c new file mode 100644 index 0000000..7efeb5c --- /dev/null +++ b/source3/nmbd/nmbd_mynames.c @@ -0,0 +1,323 @@ +/* + 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-2003 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "nmbd/nmbd.h" + +extern uint16_t samba_nb_type; /* Samba's NetBIOS type. */ + +static const char **mynames = NULL; + +static bool add_unique_netbios_name(const char *name) +{ + size_t i, num_names = talloc_array_length(mynames); + char *str = NULL; + const char **tmp = NULL; + + for (i=0; i<num_names; i++) { + if (strequal(name, mynames[i])) { + return true; + } + } + + str = talloc_strdup(NULL, name); + if (str == NULL) { + return false; + } + + tmp = talloc_realloc(NULL, mynames, const char *, num_names+1); + if (tmp == NULL) { + TALLOC_FREE(str); + return false; + } + tmp[num_names] = talloc_move(tmp, &str); + mynames = tmp; + return true; +} + +bool nmbd_init_my_netbios_names(void) +{ + const char *name = lp_netbios_name(); + const char **aliases = lp_netbios_aliases(); + + TALLOC_FREE(mynames); + + if (name[0] != '\0') { + bool ok = add_unique_netbios_name(name); + if (!ok) { + return false; + } + } + + if (aliases == NULL) { + return true; + } + + while (*aliases != NULL) { + bool ok = add_unique_netbios_name(*aliases); + if (!ok) { + return false; + } + aliases += 1; + } + + return true; +} + +const char *my_netbios_names(int i) +{ + size_t num_names = talloc_array_length(mynames); + + if ((i >= 0) && (i < num_names)) { + return mynames[i]; + } + + return NULL; +} + +/**************************************************************************** + Fail function 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_t 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) { + unstring name; + pull_ascii_nstring(name, sizeof(name), nmbname->name); + /* The name needs to be created on the unicast subnet. */ + (void)add_name_to_subnet( unicast_subnet, 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; + const char **cluster_addresses = NULL; + + 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 in any cluster addresses. We need to response to these, + * but not listen on them. This allows us to run nmbd on every + * node in the cluster, and have all of them register with a + * WINS server correctly + */ + if (lp_clustering()) { + cluster_addresses = lp_cluster_addresses(); + } + if (cluster_addresses) { + int a, n; + unsigned name_types[] = {0x20, 0x3, 0x0}; + + for (i=0; my_netbios_names(i); i++) { + for(subrec = FIRST_SUBNET; subrec; subrec = subrec->next) { + for (n=0;n<ARRAY_SIZE(name_types);n++) { + struct name_record *namerec; + struct nmb_name nmbname; + struct in_addr ip; + make_nmb_name(&nmbname, my_netbios_names(i), name_types[n]); + namerec = find_name_on_subnet(unicast_subnet, &nmbname, FIND_SELF_NAME); + if (namerec == NULL) continue; + for (a=0;cluster_addresses[a];a++) { + ip = interpret_addr2(cluster_addresses[a]); + add_ip_to_name_record(namerec, ip); + } + } + } + } + } + + /* + * 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 = subrec->namelist; namerec; namerec = nextnamerec) { + nextnamerec = namerec->next; + 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 = unicast_subnet->namelist; namerec; namerec = namerec->next) { + /* 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/source3/nmbd/nmbd_namelistdb.c b/source3/nmbd/nmbd_namelistdb.c new file mode 100644 index 0000000..141adfa --- /dev/null +++ b/source3/nmbd/nmbd_namelistdb.c @@ -0,0 +1,689 @@ +/* + 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-2003 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "system/filesys.h" +#include "nmbd/nmbd.h" + +uint16_t samba_nb_type = 0; /* samba's NetBIOS name type */ + + +/************************************************************************** + Set Samba's NetBIOS name type. +***************************************************************************/ + +void set_samba_nb_type(void) +{ + if( lp_we_are_a_wins_server() || 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. */ + } +} + +/*************************************************************************** + Convert a NetBIOS name to upper case. +***************************************************************************/ + +static bool upcase_name( struct nmb_name *target, const struct nmb_name *source ) +{ + int i; + unstring targ; + fstring scope; + + if( NULL != source ) { + memcpy( target, source, sizeof( struct nmb_name ) ); + } + + pull_ascii_nstring(targ, sizeof(targ), target->name); + if (!strupper_m( targ )) { + return false; + } + push_ascii_nstring( target->name, targ); + + pull_ascii(scope, target->scope, 64, -1, STR_TERMINATE); + if (!strupper_m( scope )) { + return false; + } + push_ascii(target->scope, scope, 64, STR_TERMINATE); + + /* 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'; + } + return true; +} + +/************************************************************************** + Remove a name from the namelist. +***************************************************************************/ + +void remove_name_from_namelist(struct subnet_record *subrec, + struct name_record *namerec ) +{ + if (subrec == wins_server_subnet) + remove_name_from_wins_namelist(namerec); + else { + subrec->namelist_changed = True; + DLIST_REMOVE(subrec->namelist, namerec); + } + + SAFE_FREE(namerec->data.ip); + ZERO_STRUCTP(namerec); + SAFE_FREE(namerec); +} + +/************************************************************************** + Find a name in a subnet. +**************************************************************************/ + +struct name_record *find_name_on_subnet(struct subnet_record *subrec, + const struct nmb_name *nmbname, + bool self_only) +{ + struct nmb_name uc_name; + struct name_record *name_ret; + + if (!upcase_name( &uc_name, nmbname )) { + return NULL; + } + + if (subrec == wins_server_subnet) { + return find_name_on_wins_subnet(&uc_name, self_only); + } + + for( name_ret = subrec->namelist; name_ret; name_ret = name_ret->next) { + if (memcmp(&uc_name, &name_ret->name, sizeof(struct nmb_name)) == 0) { + break; + } + } + + 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 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; + + for( subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec) ) { + namerec = find_name_on_subnet(subrec, nmbname, self_only); + if (namerec) { + return namerec; + } + } + + return NULL; +} + +/************************************************************************** + 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); + + if (namerec->subnet == wins_server_subnet) { + wins_store_changed_namerec(namerec); + } else { + namerec->subnet->namelist_changed = True; + } +} + +/************************************************************************** + Add an entry to a subnet name list. +***********************************************************************/ + +bool add_name_to_subnet( struct subnet_record *subrec, + const char *name, + int type, + uint16_t nb_flags, + int ttl, + enum name_source source, + int num_ips, + struct in_addr *iplist) +{ + bool ret = False; + struct name_record *namerec; + time_t time_now = time(NULL); + + if (num_ips == 0) { + return false; + } + + namerec = SMB_MALLOC_P(struct name_record); + if( NULL == namerec ) { + DEBUG( 0, ( "add_name_to_subnet: malloc fail.\n" ) ); + return False; + } + + memset( (char *)namerec, '\0', sizeof(*namerec) ); + namerec->data.ip = SMB_MALLOC_ARRAY( 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 False; + } + + namerec->subnet = subrec; + + make_nmb_name(&namerec->name, name, type); + if (!upcase_name(&namerec->name, NULL )) { + SAFE_FREE(namerec->data.ip); + SAFE_FREE(namerec); + return False; + } + + /* 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); + + 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 ) ); + + /* Now add the record to the name list. */ + + if (subrec == wins_server_subnet) { + ret = add_name_to_wins_subnet(namerec); + /* Free namerec - it's stored in the tdb. */ + SAFE_FREE(namerec->data.ip); + SAFE_FREE(namerec); + } else { + DLIST_ADD(subrec->namelist, namerec); + subrec->namelist_changed = True; + ret = True; + } + + return ret; +} + +/******************************************************************* + 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_t nb_flags, int ttl, + struct in_addr registered_ip) +{ + struct name_record *namerec; + + namerec = find_name_on_subnet( subrec, nmbname, FIND_SELF_NAME); + if (namerec == NULL) { + unstring name; + pull_ascii_nstring(name, sizeof(name), nmbname->name); + add_name_to_subnet( subrec, 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 addresses. JRA. + ******************************************************************/ + +void standard_fail_register( struct subnet_record *subrec, + 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--; + if (namerec->subnet == wins_server_subnet) { + wins_store_changed_namerec(namerec); + } else { + 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_v4( 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 = SMB_MALLOC_ARRAY( struct in_addr, namerec->data.num_ips + 1); + 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; + + if (namerec->subnet == wins_server_subnet) { + wins_store_changed_namerec(namerec); + } else { + 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_v4( 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. + NB. Does not touch the wins_subnet - no wins specific processing here. +******************************************************************/ + +static void expire_names_on_subnet(struct subnet_record *subrec, time_t t) +{ + struct name_record *namerec; + struct name_record *next_namerec; + + for( namerec = subrec->namelist; namerec; namerec = next_namerec ) { + next_namerec = namerec->next; + 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. + NB. Does not touch the wins_subnet. +******************************************************************/ + +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 = SMB_MALLOC_ARRAY( struct in_addr, num_ips); + if( NULL == iplist ) { + DEBUG(0,("add_samba_names_to_subnet: Malloc fail !\n")); + return; + } + + for( bcast_subrecs = FIRST_SUBNET, i = 0; bcast_subrecs && + i < num_ips; + bcast_subrecs = NEXT_SUBNET_EXCLUDING_UNICAST(bcast_subrecs), i++ ) { + iplist[i] = bcast_subrecs->myip; + } + num_ips = i; + } + + add_name_to_subnet(subrec,"*",0x0,samba_nb_type, PERMANENT_TTL, + PERMANENT_NAME, num_ips, iplist); + add_name_to_subnet(subrec,"*",0x20,samba_nb_type,PERMANENT_TTL, + PERMANENT_NAME, num_ips, iplist); + add_name_to_subnet(subrec,"__SAMBA__",0x20,samba_nb_type,PERMANENT_TTL, + PERMANENT_NAME, num_ips, iplist); + 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 a name_record struct. +**************************************************************************/ + +void dump_name_record( struct name_record *namerec, FILE *fp) +{ + const char *src_type; + struct tm *tm; + int i; + + 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; + } + + fprintf(fp, "Source = %s\nb_flags = %x\t", src_type, + namerec->data.nb_flags); + + if(namerec->data.death_time != PERMANENT_TTL) { + const char *asct; + tm = localtime(&namerec->data.death_time); + if (!tm) { + return; + } + asct = asctime(tm); + if (!asct) { + return; + } + fprintf(fp, "death_time = %s\t", asct); + } else { + fprintf(fp, "death_time = PERMANENT\t"); + } + + if(namerec->data.refresh_time != PERMANENT_TTL) { + const char *asct; + tm = localtime(&namerec->data.refresh_time); + if (!tm) { + return; + } + asct = asctime(tm); + if (!asct) { + return; + } + fprintf(fp, "refresh_time = %s\n", asct); + } else { + fprintf(fp, "refresh_time = PERMANENT\n"); + } + + fprintf(fp, "\t\tnumber of IPS = %d", namerec->data.num_ips); + for(i = 0; i < namerec->data.num_ips; i++) { + fprintf(fp, "\t%s", inet_ntoa(namerec->data.ip[i])); + } + + 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. +**************************************************************************/ + +static void dump_subnet_namelist(struct subnet_record *subrec, FILE *fp) +{ + struct name_record *namerec; + fprintf(fp, "Subnet %s\n----------------------\n", subrec->subnet_name); + for( namerec = subrec->namelist; namerec; namerec = namerec->next) { + dump_name_record(namerec, fp); + } +} + +/**************************************************************************** + 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) +{ + int fd; + FILE *fp; + struct subnet_record *subrec; + char *dump_path; + + dump_path = lock_path(talloc_tos(), "namelist.debug"); + if (dump_path == NULL) { + DEBUG(0, ("out of memory!\n")); + return; + } + + fd = open(dump_path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd == -1) { + DBG_ERR("Can't open file %s: %s\n", dump_path, + strerror(errno)); + return; + } + TALLOC_FREE(dump_path); + + fp = fdopen(fd, "w"); + if (!fp) { + DBG_ERR("fdopen failed: %s\n", strerror(errno)); + close(fd); + return; + } + fd = -1; + + 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_wins_subnet_namelist(fp ); + } + + fclose( fp ); +} diff --git a/source3/nmbd/nmbd_namequery.c b/source3/nmbd/nmbd_namequery.c new file mode 100644 index 0000000..ae50a23 --- /dev/null +++ b/source3/nmbd/nmbd_namequery.c @@ -0,0 +1,276 @@ +/* + 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-2003 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "nmbd/nmbd.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_v4(&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. */ + if (nmb->answers) { + rrec->repeat_time = p->timestamp + nmb->answers->ttl; + } else { + /* No answer - this is probably a corrupt + packet.... */ + DEBUG(0,("query_name_response: missing answer record in " + "NMB_WACK_OPCODE response.\n")); + rrec->repeat_time = p->timestamp + 10; + } + 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, const 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, + const 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/source3/nmbd/nmbd_nameregister.c b/source3/nmbd/nmbd_nameregister.c new file mode 100644 index 0000000..4b7b6c6 --- /dev/null +++ b/source3/nmbd/nmbd_nameregister.c @@ -0,0 +1,608 @@ +/* + 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-2003 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "nmbd/nmbd.h" +#include "lib/util/string_wrappers.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_t 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 definitely 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 */ + unstring ans_name; + pull_ascii_nstring(ans_name, sizeof(ans_name), answer_name->name); + if((nmb->header.rcode == ACT_ERR) && strequal(lp_workgroup(), ans_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 { + if (!ip_equal_v4(rrec->packet->ip, p->ip)) { + DEBUG(5,("register_name_response: Ignoring WINS server response " + "from IP %s, for name %s. We sent to IP %s\n", + inet_ntoa(p->ip), + nmb_namestr(answer_name), + inet_ntoa(rrec->packet->ip))); + return; + } + /* 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 { + struct nmb_name qname = *question_name; + if( rrec->fail_fn) + (*(register_name_fail_function)rrec->fail_fn)(subrec, rrec, question_name); + /* Remove the name. */ + standard_fail_register( subrec, &qname); + } + + /* 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_t 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_t 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 { + struct nmb_name qname = *question_name; + if( rrec->fail_fn) + (*(register_name_fail_function)rrec->fail_fn)(subrec, rrec, question_name); + /* Remove the name. */ + standard_fail_register( subrec, &qname); + } + + /* 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_t 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 *)SMB_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_t 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_v4(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_t 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; + unstring name; + + for(subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec) ) + num_ips++; + + if((ip_list = SMB_MALLOC_ARRAY(struct in_addr, num_ips))==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; + } + + pull_ascii_nstring(name, sizeof(name), nmbname->name); + add_name_to_subnet(unicast_subnet, 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_t nb_flags, + register_name_success_function success_fn, + register_name_fail_function fail_fn, + struct userdata_struct *userdata) +{ + struct nmb_name nmbname; + nstring nname; + size_t converted_size; + + errno = 0; + converted_size = push_ascii_nstring(nname, name); + if (converted_size != (size_t)-1) { + /* Success. */ + make_nmb_name(&nmbname, name, type); + } else if (errno == E2BIG) { + /* + * Name converted to CH_DOS is too large. + * try to truncate. + */ + char *converted_str_dos = NULL; + char *converted_str_unix = NULL; + bool ok; + + converted_size = 0; + + ok = convert_string_talloc(talloc_tos(), + CH_UNIX, + CH_DOS, + name, + strlen(name)+1, + &converted_str_dos, + &converted_size); + if (!ok) { + DEBUG(0,("register_name: NetBIOS name %s cannot be " + "converted. Failing to register name.\n", + name)); + return; + } + + /* + * As it's now CH_DOS codepage + * we truncate by writing '\0' at + * MAX_NETBIOSNAME_LEN-1 and then + * convert back to CH_UNIX which we + * need for the make_nmb_name() call. + */ + if (converted_size >= MAX_NETBIOSNAME_LEN) { + converted_str_dos[MAX_NETBIOSNAME_LEN-1] = '\0'; + } + + ok = convert_string_talloc(talloc_tos(), + CH_DOS, + CH_UNIX, + converted_str_dos, + strlen(converted_str_dos)+1, + &converted_str_unix, + &converted_size); + if (!ok) { + DEBUG(0,("register_name: NetBIOS name %s cannot be " + "converted back to CH_UNIX. " + "Failing to register name.\n", + converted_str_dos)); + TALLOC_FREE(converted_str_dos); + return; + } + + make_nmb_name(&nmbname, converted_str_unix, type); + + TALLOC_FREE(converted_str_dos); + TALLOC_FREE(converted_str_unix); + } else { + /* + * Generic conversion error. Fail to register. + */ + DEBUG(0,("register_name: NetBIOS name %s cannot be " + "converted (%s). Failing to register name.\n", + name, strerror(errno))); + return; + } + + /* 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/source3/nmbd/nmbd_namerelease.c b/source3/nmbd/nmbd_namerelease.c new file mode 100644 index 0000000..4f34a45 --- /dev/null +++ b/source3/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 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "nmbd/nmbd.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/source3/nmbd/nmbd_nodestatus.c b/source3/nmbd/nmbd_nodestatus.c new file mode 100644 index 0000000..1f3e1cf --- /dev/null +++ b/source3/nmbd/nmbd_nodestatus.c @@ -0,0 +1,92 @@ +/* + 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-2003 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "nmbd/nmbd.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/source3/nmbd/nmbd_packets.c b/source3/nmbd/nmbd_packets.c new file mode 100644 index 0000000..a1d8dee --- /dev/null +++ b/source3/nmbd/nmbd_packets.c @@ -0,0 +1,2262 @@ +/* + 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-2003 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "nmbd/nmbd.h" +#include "../lib/util/select.h" +#include "system/select.h" +#include "libsmb/libsmb.h" +#include "libsmb/unexpected.h" +#include "lib/util/string_wrappers.h" + +extern int ClientNMB; +extern int ClientDGRAM; +extern int global_nmb_port; + +extern int num_response_packets; + +bool rescan_listen_set = False; + +static struct nb_packet_server *packet_server; + +bool nmbd_init_packet_server(void) +{ + NTSTATUS status; + + status = nb_packet_server_create( + NULL, nmbd_event_context(), + lp_parm_int(-1, "nmbd", "unexpected_clients", 200), + &packet_server); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("ERROR: nb_packet_server_create failed: %s\n", + nt_errstr(status))); + return false; + } + return true; +} + + +/******************************************************************* + 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_v4(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_v4(local_ip, subrec->myip)) + return subrec->dgram_sock; + + return ClientDGRAM; +} + +/*************************************************************************** +Get/Set problematic nb_flags as network byte order 16 bit int. +**************************************************************************/ + +uint16_t get_nb_flags(char *buf) +{ + return ((((uint16_t)*buf)&0xFFFF) & NB_FLGMSK); +} + +void set_nb_flags(char *buf, uint16_t nb_flags) +{ + *buf++ = ((nb_flags & NB_FLGMSK) & 0xFF); + *buf = '\0'; +} + +/*************************************************************************** +Dumps out the browse packet data. +**************************************************************************/ + +static void debug_browse_data(const 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_t name_trn_id=0; + +static uint16_t generate_name_trn_id(void) +{ + if (!name_trn_id) { + name_trn_id = ((unsigned)time(NULL)%(unsigned)0x7FFF) + ((unsigned)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_v4(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 = SMB_MALLOC_P(struct packet_struct)) == 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->recv_fd = -1; + packet->send_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_t nb_flags, + const struct in_addr *register_ip) +{ + struct nmb_packet *nmb = &packet->packet.nmb; + + if((nmb->additional = SMB_MALLOC_P(struct res_rec)) == NULL) { + DEBUG(0,("create_and_init_additional_record: 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->recv_fd = -1; + packet->send_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_t nb_flags, const 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_t 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_t 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_t 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_t nb_flags) +{ + struct packet_struct *p; + struct response_record *rrec; + struct sockaddr_storage ss; + const struct sockaddr_storage *pss = NULL; + 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; + + in_addr_to_sockaddr_storage(&ss, subrec->bcast_ip); + pss = iface_ip((struct sockaddr *)(void *)&ss); + if (!pss || pss->ss_family != AF_INET) { + p->locked = False; + free_packet(p); + return NULL; + } + + if(initiate_name_register_packet(p, nb_flags, + &((const struct sockaddr_in *)pss)->sin_addr) == 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_t 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 *)SMB_MALLOC(sizeof(*userdata) + strlen(tag) + 1); + if (!userdata) { + p->locked = False; + free_packet(p); + 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_t 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_t 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_v4(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++) { + const struct in_addr *ifip = iface_n_ip_v4(i); + + if (ifip == NULL) { + DEBUG(0,("queue_query_name: interface %d has NULL IP address !\n", i)); + continue; + } + + if (is_loopback_ip_v4(*ifip)) { + 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->send_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; + int rr_type = RR_TYPE_NB; + const char *packet_type = "unknown"; + + /* Check if we are sending to or from ourselves. */ + if(ismyip_v4(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; + rr_type = RR_TYPE_NBSTAT; + break; + case NMB_QUERY: + packet_type = "nmb_query"; + nmb->header.nm_flags.recursion_desired = True; + nmb->header.nm_flags.recursion_available = True; + if (rcode) { + rr_type = RR_TYPE_NULL; + } + 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; + rr_type = RR_TYPE_NULL; + 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; + if (rcode) { + rr_type = RR_TYPE_NULL; + } + 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 %d\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 = rr_type; + nmb->answers->rr_class = RR_CLASS_IN; + nmb->answers->ttl = ttl; + + if (data && len) { + if (len < 0 || len > sizeof(nmb->answers->rdata)) { + DEBUG(5,("reply_netbios_packet: " + "invalid packet len (%d)\n", + len )); + return; + } + nmb->answers->rdlength = len; + memcpy(nmb->answers->rdata, data, len); + } + + packet.packet_type = NMB_PACKET; + packet.recv_fd = -1; + /* Ensure we send out on the same fd that the original + packet came in on to give the correct source IP address. */ + if (orig_packet->send_fd != -1) { + packet.send_fd = orig_packet->send_fd; + } else { + packet.send_fd = orig_packet->recv_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 +******************************************************************/ + +void queue_packet(struct packet_struct *packet) +{ + DLIST_ADD_END(packet_queue, packet); +} + +/**************************************************************************** + 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_v4(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, const 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); + char scope[64]; + unstring src_name; + + /* Drop the packet if it's a different NetBIOS scope, or the source is from one of our names. */ + pull_ascii(scope, dgram->dest_name.scope, 64, 64, STR_TERMINATE); + if (!strequal(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), scope, lp_netbios_scope())); + return; + } + + pull_ascii_nstring(src_name, sizeof(src_name), dgram->source_name.name); + if (is_myname(src_name)) { + DEBUG(7,("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))); + break; + } +} + +/**************************************************************************** + Dispatch a LanMan browse frame from port 138 to the correct processing function. +****************************************************************************/ + +static void process_lanman_packet(struct packet_struct *p, const 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); + char scope[64]; + unstring src_name; + + /* Drop the packet if it's a different NetBIOS scope, or the source is from one of our names. */ + + pull_ascii(scope, dgram->dest_name.scope, 64, 64, STR_TERMINATE); + if (!strequal(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), scope, lp_netbios_scope())); + return; + } + + pull_ascii_nstring(src_name, sizeof(src_name), dgram->source_name.name); + if (is_myname(src_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, len > 1 ? len-1 : 0); + break; + case ANN_AnnouncementRequest: + process_lm_announce_request(subrec, p, buf+1, len > 1 ? len-1 : 0); + 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))); + break; + } +} + +/**************************************************************************** + 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_v4(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) +{ + const char *buf; + const 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)) { + nb_packet_dispatch(packet_server, 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) { + nb_packet_dispatch(packet_server, 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; + } + + /* Ensure we have a large enough packet before looking inside. */ + if (dgram->datasize < (smb_vwv12 - 2)) { + /* That's the offset minus the 4 byte length + 2 bytes of offset. */ + DEBUG(0,("process_dgram: ignoring too short dgram packet (%u) sent to name %s from IP %s\n", + (unsigned int)dgram->datasize, + nmb_namestr(&dgram->dest_name), + inet_ntoa(p->ip) )); + 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 || len > dgram->datasize) { + DEBUG(0,("process_dgram: ignoring malformed1 (datasize = %d, len = %d) datagram \ +packet sent to name %s from IP %s\n", + dgram->datasize, + len, + nmb_namestr(&dgram->dest_name), + inet_ntoa(p->ip) )); + return; + } + + if (buf2 < dgram->data || (buf2 >= dgram->data + dgram->datasize)) { + DEBUG(0,("process_dgram: ignoring malformed2 (datasize = %d, len=%d, off=%d) datagram \ +packet sent to name %s from IP %s\n", + dgram->datasize, + len, + (int)PTR_DIFF(buf2, dgram->data), + nmb_namestr(&dgram->dest_name), + inet_ntoa(p->ip) )); + return; + } + + if ((buf2 + len < dgram->data) || (buf2 + len > dgram->data + dgram->datasize)) { + DEBUG(0,("process_dgram: ignoring malformed3 (datasize = %d, len=%d, off=%d) datagram \ +packet sent to name %s from IP %s\n", + dgram->datasize, + len, + (int)PTR_DIFF(buf2, dgram->data), + nmb_namestr(&dgram->dest_name), + inet_ntoa(p->ip) )); + return; + } + + 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_const(buf),CVAL(buf2,0),len)); + + /* Datagram packet received for the browser mailslot */ + if (strequal(smb_buf_const(buf),BROWSE_MAILSLOT)) { + process_browse_packet(p,buf2,len); + return; + } + + /* Datagram packet received for the LAN Manager mailslot */ + if (strequal(smb_buf_const(buf),LANMAN_MAILSLOT)) { + process_lanman_packet(p,buf2,len); + return; + } + + /* Datagram packet received for the domain logon mailslot */ + if (strequal(smb_buf_const(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_const(buf),NT_LOGON_MAILSLOT)) { + process_logon_packet(p,buf2,len,NT_LOGON_MAILSLOT); + return; + } + + nb_packet_dispatch(packet_server, 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 %d\n", + nmb->header.name_trn_id)); + nb_packet_dispatch(packet_server, p); + return NULL; + } + + if(subrec == NULL) { + DEBUG(0, ("find_subnet_for_nmb_packet: subnet record " + "not found for response id %d\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_v4(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 = %d. 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)) { + DLIST_REMOVE(packet_queue, p); + + 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; + + restart: + + 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); + } + /* We have changed subrec->responselist, + * restart from the beginning of this list. */ + goto restart; + } /* !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. +***************************************************************************/ + +struct socket_attributes { + enum packet_type type; + bool broadcast; + int fd; + bool triggered; +}; + +static bool create_listen_array(struct socket_attributes **pattrs, + int *pnum_sockets) +{ + struct subnet_record *subrec = NULL; + int count = 0; + int num = 0; + struct socket_attributes *attrs; + + /* The ClientNMB and ClientDGRAM sockets */ + count = 2; + + /* Check that we can add all the fd's we need. */ + for (subrec = FIRST_SUBNET; + subrec != NULL; + subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) { + if (subrec->nmb_sock != -1) { + count += 1; + } + if (subrec->dgram_sock != -1) { + count += 1; + } + if (subrec->nmb_bcast != -1) { + count += 1; + } + if (subrec->dgram_bcast != -1) { + count += 1; + } + } + + attrs = talloc_zero_array(NULL, struct socket_attributes, count); + if (attrs == NULL) { + DEBUG(1, ("talloc fail for attrs. " + "size %d\n", count)); + return true; + } + + num = 0; + + attrs[num].fd = ClientNMB; + attrs[num].type = NMB_PACKET; + attrs[num].broadcast = false; + num += 1; + + attrs[num].fd = ClientDGRAM; + attrs[num].type = DGRAM_PACKET; + attrs[num].broadcast = false; + num += 1; + + for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) { + + if (subrec->nmb_sock != -1) { + attrs[num].fd = subrec->nmb_sock; + attrs[num].type = NMB_PACKET; + attrs[num].broadcast = false; + num += 1; + } + + if (subrec->nmb_bcast != -1) { + attrs[num].fd = subrec->nmb_bcast; + attrs[num].type = NMB_PACKET; + attrs[num].broadcast = true; + num += 1; + } + + if (subrec->dgram_sock != -1) { + attrs[num].fd = subrec->dgram_sock; + attrs[num].type = DGRAM_PACKET; + attrs[num].broadcast = false; + num += 1; + } + + if (subrec->dgram_bcast != -1) { + attrs[num].fd = subrec->dgram_bcast; + attrs[num].type = DGRAM_PACKET; + attrs[num].broadcast = true; + num += 1; + } + } + + TALLOC_FREE(*pattrs); + *pattrs = attrs; + + *pnum_sockets = count; + + return False; +} + +/**************************************************************************** + List of packets we're processing this select. +***************************************************************************/ + +struct processed_packet { + struct processed_packet *next; + struct processed_packet *prev; + enum packet_type packet_type; + struct in_addr ip; + int packet_id; +}; + +/**************************************************************************** + Have we seen this before ? +***************************************************************************/ + +static bool is_processed_packet(struct processed_packet *processed_packet_list, + struct packet_struct *packet) +{ + struct processed_packet *p = NULL; + + for (p = processed_packet_list; p; p = p->next) { + if (ip_equal_v4(p->ip, packet->ip) && p->packet_type == packet->packet_type) { + if ((p->packet_type == NMB_PACKET) && + (p->packet_id == + packet->packet.nmb.header.name_trn_id)) { + return true; + } else if ((p->packet_type == DGRAM_PACKET) && + (p->packet_id == + packet->packet.dgram.header.dgm_id)) { + return true; + } + } + } + return false; +} + +/**************************************************************************** + Keep a list of what we've seen before. +***************************************************************************/ + +static bool store_processed_packet(struct processed_packet **pp_processed_packet_list, + struct packet_struct *packet) +{ + struct processed_packet *p = SMB_MALLOC_P(struct processed_packet); + if (!p) { + return false; + } + p->packet_type = packet->packet_type; + p->ip = packet->ip; + if (packet->packet_type == NMB_PACKET) { + p->packet_id = packet->packet.nmb.header.name_trn_id; + } else if (packet->packet_type == DGRAM_PACKET) { + p->packet_id = packet->packet.dgram.header.dgm_id; + } else { + SAFE_FREE(p); + return false; + } + + DLIST_ADD(*pp_processed_packet_list, p); + return true; +} + +/**************************************************************************** + Throw away what we've seen before. +***************************************************************************/ + +static void free_processed_packet_list(struct processed_packet **pp_processed_packet_list) +{ + struct processed_packet *p = NULL, *next = NULL; + + for (p = *pp_processed_packet_list; p; p = next) { + next = p->next; + DLIST_REMOVE(*pp_processed_packet_list, p); + SAFE_FREE(p); + } +} + +/**************************************************************************** + Timeout callback - just notice we timed out. +***************************************************************************/ + +static void nmbd_timeout_handler(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + bool *got_timeout = private_data; + *got_timeout = true; +} + +/**************************************************************************** + fd callback - remember the fd that triggered. +***************************************************************************/ + +static void nmbd_fd_handler(struct tevent_context *ev, + struct tevent_fd *fde, + uint16_t flags, + void *private_data) +{ + struct socket_attributes *attr = private_data; + attr->triggered = true; +} + +/**************************************************************************** + Read from a socket. +****************************************************************************/ + +static ssize_t read_udp_v4_socket( + int fd, + char *buf, + size_t len, + struct sockaddr_storage *psa) +{ + ssize_t ret; + socklen_t socklen = sizeof(*psa); + struct sockaddr_in *si = (struct sockaddr_in *)psa; + + memset((char *)psa,'\0',socklen); + + ret = (ssize_t)sys_recvfrom(fd,buf,len,0, + (struct sockaddr *)psa,&socklen); + if (ret <= 0) { + /* Don't print a low debug error for a non-blocking socket. */ + if (errno == EAGAIN) { + DEBUG(10,("read_udp_v4_socket: returned EAGAIN\n")); + } else { + DEBUG(2,("read_udp_v4_socket: failed. errno=%s\n", + strerror(errno))); + } + return 0; + } + + if (psa->ss_family != AF_INET) { + DEBUG(2,("read_udp_v4_socket: invalid address family %d " + "(not IPv4)\n", (int)psa->ss_family)); + return 0; + } + + DEBUG(10,("read_udp_v4_socket: ip %s port %d read: %lu\n", + inet_ntoa(si->sin_addr), + si->sin_port, + (unsigned long)ret)); + + return ret; +} + +/******************************************************************* + Read a packet from a socket and parse it, returning a packet ready + to be used or put on the queue. This assumes a UDP socket. +******************************************************************/ + +static struct packet_struct *read_packet(int fd,enum packet_type packet_type) +{ + struct packet_struct *packet; + struct sockaddr_storage sa; + struct sockaddr_in *si = (struct sockaddr_in *)&sa; + char buf[MAX_DGRAM_SIZE]; + int length; + + length = read_udp_v4_socket(fd,buf,sizeof(buf),&sa); + if (length < MIN_DGRAM_SIZE || sa.ss_family != AF_INET) { + return NULL; + } + + packet = parse_packet(buf, + length, + packet_type, + si->sin_addr, + ntohs(si->sin_port)); + if (!packet) + return NULL; + + packet->recv_fd = fd; + packet->send_fd = -1; + + DEBUG(5,("Received a packet of len %d from (%s) port %d\n", + length, inet_ntoa(packet->ip), packet->port ) ); + + return(packet); +} + +/**************************************************************************** + Listens for NMB or DGRAM packets, and queues them. + return True if the socket is dead +***************************************************************************/ + +bool listen_for_packets(struct messaging_context *msg, bool run_election) +{ + static struct socket_attributes *attrs = NULL; + static int listen_number = 0; + int num_sockets; + int i; + int loop_rtn; + int timeout_secs; + +#ifndef SYNC_DNS + int dns_fd; + int dns_pollidx = -1; +#endif + struct processed_packet *processed_packet_list = NULL; + struct tevent_timer *te = NULL; + bool got_timeout = false; + TALLOC_CTX *frame = talloc_stackframe(); + + if ((attrs == NULL) || rescan_listen_set) { + if (create_listen_array(&attrs, &listen_number)) { + DEBUG(0,("listen_for_packets: Fatal error. unable to create listen set. Exiting.\n")); + TALLOC_FREE(frame); + return True; + } + rescan_listen_set = False; + } + + num_sockets = listen_number; + +#ifndef SYNC_DNS + dns_fd = asyncdns_fd(); + if (dns_fd != -1) { + attrs = talloc_realloc(NULL, + attrs, + struct socket_attributes, + num_sockets + 1); + if (attrs == NULL) { + TALLOC_FREE(frame); + return true; + } + dns_pollidx = num_sockets; + attrs[dns_pollidx].fd = dns_fd; + /* + * dummy values, we only need + * fd and triggered. + */ + attrs[dns_pollidx].type = NMB_PACKET; + attrs[dns_pollidx].broadcast = false; + num_sockets += 1; + } +#endif + + for (i=0; i<num_sockets; i++) { + struct tevent_fd *tfd = tevent_add_fd(nmbd_event_context(), + frame, + attrs[i].fd, + TEVENT_FD_READ, + nmbd_fd_handler, + &attrs[i]); + if (tfd == NULL) { + TALLOC_FREE(frame); + return true; + } + attrs[i].triggered = false; + } + + /* + * 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. + */ + + if (run_election||num_response_packets) { + timeout_secs = 1; + } else { + timeout_secs = NMBD_SELECT_LOOP; + } + + te = tevent_add_timer(nmbd_event_context(), + frame, + tevent_timeval_current_ofs(timeout_secs, 0), + nmbd_timeout_handler, + &got_timeout); + if (te == NULL) { + TALLOC_FREE(frame); + return true; + } + + loop_rtn = tevent_loop_once(nmbd_event_context()); + + if (loop_rtn == -1) { + TALLOC_FREE(frame); + return true; + } + + if (got_timeout) { + TALLOC_FREE(frame); + return false; + } + +#ifndef SYNC_DNS + if ((dns_fd != -1) && (dns_pollidx != -1) && + attrs[dns_pollidx].triggered){ + run_dns_queue(msg); + TALLOC_FREE(frame); + return false; + } +#endif + + for(i = 0; i < listen_number; i++) { + enum packet_type packet_type; + struct packet_struct *packet; + const char *packet_name; + int client_fd; + int client_port; + + if (!attrs[i].triggered) { + continue; + } + + if (attrs[i].type == NMB_PACKET) { + /* Port 137 */ + packet_type = NMB_PACKET; + packet_name = "nmb"; + client_fd = ClientNMB; + client_port = global_nmb_port; + } else { + /* Port 138 */ + packet_type = DGRAM_PACKET; + packet_name = "dgram"; + client_fd = ClientDGRAM; + client_port = DGRAM_PORT; + } + + packet = read_packet(attrs[i].fd, packet_type); + if (!packet) { + continue; + } + + /* + * 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() && + (attrs[i].fd == client_fd) && + (!is_local_net_v4(packet->ip))) { + DEBUG(7,("discarding %s packet sent to broadcast socket from %s:%d\n", + packet_name, inet_ntoa(packet->ip), packet->port)); + free_packet(packet); + continue; + } + + if (!IS_DC) { + if ((is_loopback_ip_v4(packet->ip) || ismyip_v4(packet->ip)) && + packet->port == client_port) + { + if (client_port == DGRAM_PORT) { + DEBUG(7,("discarding own dgram packet from %s:%d\n", + inet_ntoa(packet->ip),packet->port)); + free_packet(packet); + continue; + } + + if (packet->packet.nmb.header.nm_flags.bcast) { + DEBUG(7,("discarding own nmb bcast packet from %s:%d\n", + inet_ntoa(packet->ip),packet->port)); + free_packet(packet); + continue; + } + } + } + + if (is_processed_packet(processed_packet_list, packet)) { + DEBUG(7,("discarding duplicate packet from %s:%d\n", + inet_ntoa(packet->ip),packet->port)); + free_packet(packet); + continue; + } + + store_processed_packet(&processed_packet_list, packet); + + if (attrs[i].broadcast) { + /* this is a broadcast socket */ + packet->send_fd = attrs[i-1].fd; + } else { + /* this is already a unicast socket */ + packet->send_fd = attrs[i].fd; + } + + queue_packet(packet); + } + + free_processed_packet_list(&processed_packet_list); + TALLOC_FREE(frame); + return False; +} + +/**************************************************************************** + Construct and send a netbios DGRAM. +**************************************************************************/ + +bool send_mailslot(bool unique, const char *mailslot,char *buf, size_t 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_v4(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); + + if (smb_size + 17*2 + strlen(mailslot) + 1 + len > MAX_DGRAM_SIZE) { + DEBUG(0, ("send_mailslot: Cannot write beyond end of packet\n")); + return false; + } + + cli_set_message(ptr,17,strlen(mailslot) + 1 + 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); + strlcpy_base(p2, mailslot, dgram->data, sizeof(dgram->data)); + p2 = skip_string(ptr,MAX_DGRAM_SIZE,p2); + + if (((p2+len) > dgram->data+sizeof(dgram->data)) || ((p2+len) < p2)) { + DEBUG(0, ("send_mailslot: Cannot write beyond end of packet\n")); + return False; + } else { + if (len) { + 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.recv_fd = -1; + p.send_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/source3/nmbd/nmbd_processlogon.c b/source3/nmbd/nmbd_processlogon.c new file mode 100644 index 0000000..3e86289 --- /dev/null +++ b/source3/nmbd/nmbd_processlogon.c @@ -0,0 +1,570 @@ +/* + 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-2003 + Copyright (C) Jim McDonough <jmcd@us.ibm.com> 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 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + + Revision History: + +*/ + +#include "includes.h" +#include "../libcli/netlogon/netlogon.h" +#include "../libcli/cldap/cldap.h" +#include "../lib/tsocket/tsocket.h" +#include "../libcli/security/security.h" +#include "secrets.h" +#include "nmbd/nmbd.h" + +struct sam_database_info { + uint32_t index; + uint32_t serial_lo, serial_hi; + uint32_t date_lo, date_hi; +}; + +/** + * check whether the client belongs to the hosts + * for which initial logon should be delayed... + */ +static bool delay_logon(const char *peer_name, const char *peer_addr) +{ + const char **delay_list = lp_init_logon_delayed_hosts(); + const char *peer[2]; + + if (delay_list == NULL) { + return False; + } + + peer[0] = peer_name; + peer[1] = peer_addr; + + return list_match(delay_list, (const char *)peer, client_match); +} + +static void delayed_init_logon_handler(struct tevent_context *event_ctx, + struct tevent_timer *te, + struct timeval now, + void *private_data) +{ + struct packet_struct *p = (struct packet_struct *)private_data; + + DEBUG(10, ("delayed_init_logon_handler (%lx): re-queuing packet.\n", + (unsigned long)te)); + + queue_packet(p); + + TALLOC_FREE(te); +} + +struct nmbd_proxy_logon_context { + struct cldap_socket *cldap_sock; +}; + +static struct nmbd_proxy_logon_context *global_nmbd_proxy_logon; + +bool initialize_nmbd_proxy_logon(void) +{ + const char *cldap_server = lp_parm_const_string(-1, "nmbd_proxy_logon", + "cldap_server", NULL); + struct nmbd_proxy_logon_context *ctx; + NTSTATUS status; + struct in_addr addr; + char addrstr[INET_ADDRSTRLEN]; + const char *server_str; + int ret; + struct tsocket_address *server_addr; + + if (!cldap_server) { + return true; + } + + addr = interpret_addr2(cldap_server); + server_str = inet_ntop(AF_INET, &addr, + addrstr, sizeof(addrstr)); + if (!server_str || strcmp("0.0.0.0", server_str) == 0) { + DEBUG(0,("Failed to resolve[%s] for nmbd_proxy_logon\n", + cldap_server)); + return false; + } + + ctx = talloc_zero(nmbd_event_context(), + struct nmbd_proxy_logon_context); + if (!ctx) { + return false; + } + + ret = tsocket_address_inet_from_strings(ctx, "ipv4", + server_str, LDAP_PORT, + &server_addr); + if (ret != 0) { + TALLOC_FREE(ctx); + status = map_nt_error_from_unix(errno); + DEBUG(0,("Failed to create cldap tsocket_address for %s - %s\n", + server_str, nt_errstr(status))); + return false; + } + + /* we create a connected udp socket */ + status = cldap_socket_init(ctx, NULL, server_addr, &ctx->cldap_sock); + TALLOC_FREE(server_addr); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(ctx); + DEBUG(0,("failed to create cldap socket for %s: %s\n", + server_str, nt_errstr(status))); + return false; + } + + global_nmbd_proxy_logon = ctx; + return true; +} + +struct nmbd_proxy_logon_state { + struct in_addr local_ip; + struct packet_struct *p; + const char *remote_name; + uint8_t remote_name_type; + const char *remote_mailslot; + struct nbt_netlogon_packet req; + struct nbt_netlogon_response resp; + struct cldap_netlogon io; +}; + +static int nmbd_proxy_logon_state_destructor(struct nmbd_proxy_logon_state *s) +{ + s->p->locked = false; + free_packet(s->p); + return 0; +} + +static void nmbd_proxy_logon_done(struct tevent_req *subreq); + +static void nmbd_proxy_logon(struct nmbd_proxy_logon_context *ctx, + struct in_addr local_ip, + struct packet_struct *p, + const uint8_t *buf, + uint32_t len) +{ + struct nmbd_proxy_logon_state *state; + enum ndr_err_code ndr_err; + DATA_BLOB blob = data_blob_const(buf, len); + const char *computer_name = NULL; + const char *mailslot_name = NULL; + const char *user_name = NULL; + const char *domain_sid = NULL; + uint32_t acct_control = 0; + uint32_t nt_version = 0; + struct tevent_req *subreq; + fstring source_name; + struct dgram_packet *dgram = &p->packet.dgram; + + state = talloc_zero(ctx, struct nmbd_proxy_logon_state); + if (!state) { + DEBUG(0,("failed to allocate nmbd_proxy_logon_state\n")); + return; + } + + pull_ascii_nstring(source_name, sizeof(source_name), dgram->source_name.name); + state->remote_name = talloc_strdup(state, source_name); + state->remote_name_type = dgram->source_name.name_type, + state->local_ip = local_ip; + state->p = p; + + ndr_err = ndr_pull_struct_blob( + &blob, state, &state->req, + (ndr_pull_flags_fn_t)ndr_pull_nbt_netlogon_packet); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + NTSTATUS status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("failed parse nbt_netlogon_packet: %s\n", + nt_errstr(status))); + TALLOC_FREE(state); + return; + } + + if (DEBUGLEVEL >= 10) { + DEBUG(10, ("nmbd_proxy_logon:\n")); + NDR_PRINT_DEBUG(nbt_netlogon_packet, &state->req); + } + + switch (state->req.command) { + case LOGON_SAM_LOGON_REQUEST: + computer_name = state->req.req.logon.computer_name; + user_name = state->req.req.logon.user_name; + mailslot_name = state->req.req.logon.mailslot_name; + acct_control = state->req.req.logon.acct_control; + if (state->req.req.logon.sid_size > 0) { + domain_sid = dom_sid_string(state, + &state->req.req.logon.sid); + if (!domain_sid) { + DEBUG(0,("failed to get a string for sid\n")); + TALLOC_FREE(state); + return; + } + } + nt_version = state->req.req.logon.nt_version; + break; + + default: + /* this can't happen as the caller already checks the command */ + break; + } + + state->remote_mailslot = mailslot_name; + + if (user_name && strlen(user_name) == 0) { + user_name = NULL; + } + + if (computer_name && strlen(computer_name) == 0) { + computer_name = NULL; + } + + /* + * as the socket is connected, + * we don't need to specify the destination + */ + state->io.in.dest_address = NULL; + state->io.in.dest_port = 0; + state->io.in.realm = NULL; + state->io.in.host = computer_name; + state->io.in.user = user_name; + state->io.in.domain_guid = NULL; + state->io.in.domain_sid = domain_sid; + state->io.in.acct_control = acct_control; + state->io.in.version = nt_version; + state->io.in.map_response = false; + + subreq = cldap_netlogon_send(state, nmbd_event_context(), + ctx->cldap_sock, + &state->io); + if (!subreq) { + DEBUG(0,("failed to send cldap netlogon call\n")); + TALLOC_FREE(state); + return; + } + tevent_req_set_callback(subreq, nmbd_proxy_logon_done, state); + + /* we reply async */ + state->p->locked = true; + talloc_set_destructor(state, nmbd_proxy_logon_state_destructor); +} + +static void nmbd_proxy_logon_done(struct tevent_req *subreq) +{ + struct nmbd_proxy_logon_state *state = + tevent_req_callback_data(subreq, + struct nmbd_proxy_logon_state); + NTSTATUS status; + DATA_BLOB response = data_blob_null; + + status = cldap_netlogon_recv(subreq, state, &state->io); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("failed to recv cldap netlogon call: %s\n", + nt_errstr(status))); + TALLOC_FREE(state); + return; + } + + status = push_netlogon_samlogon_response(&response, state, + &state->io.out.netlogon); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("failed to push netlogon_samlogon_response: %s\n", + nt_errstr(status))); + TALLOC_FREE(state); + return; + } + + send_mailslot(true, state->remote_mailslot, + (char *)response.data, response.length, + lp_netbios_name(), 0x0, + state->remote_name, + state->remote_name_type, + state->p->ip, + state->local_ip, + state->p->port); + TALLOC_FREE(state); +} + +/**************************************************************************** +Process a domain logon packet +**************************************************************************/ + +void process_logon_packet(struct packet_struct *p, const char *buf,int len, + const char *mailslot) +{ + fstring source_name; + struct dgram_packet *dgram = &p->packet.dgram; + struct sockaddr_storage ss; + const struct sockaddr_storage *pss; + struct in_addr ip; + + DATA_BLOB blob_in, blob_out; + enum ndr_err_code ndr_err; + struct nbt_netlogon_packet request; + struct nbt_netlogon_response response; + NTSTATUS status; + const char *pdc_name; + + in_addr_to_sockaddr_storage(&ss, p->ip); + pss = iface_ip((struct sockaddr *)&ss); + if (!pss) { + DEBUG(5,("process_logon_packet:can't find outgoing interface " + "for packet from IP %s\n", + inet_ntoa(p->ip) )); + return; + } + ip = ((const struct sockaddr_in *)pss)->sin_addr; + + if (!IS_DC) { + DEBUG(5,("process_logon_packet: Logon packet received from IP %s and domain \ +logons are not enabled.\n", inet_ntoa(p->ip) )); + return; + } + + pull_ascii_nstring(source_name, sizeof(source_name), dgram->source_name.name); + + pdc_name = talloc_asprintf(talloc_tos(), "\\\\%s", lp_netbios_name()); + if (!pdc_name) { + return; + } + + ZERO_STRUCT(request); + + blob_in = data_blob_const(buf, len); + + ndr_err = ndr_pull_struct_blob(&blob_in, talloc_tos(), &request, + (ndr_pull_flags_fn_t)ndr_pull_nbt_netlogon_packet); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DEBUG(1,("process_logon_packet: Failed to pull logon packet\n")); + return; + } + + if (DEBUGLEVEL >= 10) { + NDR_PRINT_DEBUG(nbt_netlogon_packet, &request); + } + + DEBUG(4,("process_logon_packet: Logon from %s: code = 0x%x\n", + inet_ntoa(p->ip), request.command)); + + switch (request.command) { + case LOGON_REQUEST: { + + struct nbt_netlogon_response2 response2; + + DEBUG(5,("process_logon_packet: Domain login request from %s at IP %s user=%s token=%x\n", + request.req.logon0.computer_name, inet_ntoa(p->ip), + request.req.logon0.user_name, + request.req.logon0.lm20_token)); + + response2.command = LOGON_RESPONSE2; + response2.pdc_name = pdc_name; + response2.lm20_token = 0xffff; + + response.response_type = NETLOGON_RESPONSE2; + response.data.response2 = response2; + + status = push_nbt_netlogon_response(&blob_out, talloc_tos(), &response); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("process_logon_packet: failed to push packet\n")); + return; + } + + if (DEBUGLEVEL >= 10) { + NDR_PRINT_DEBUG(nbt_netlogon_response2, &response.data.response2); + } + + send_mailslot(True, request.req.logon0.mailslot_name, + (char *)blob_out.data, + blob_out.length, + lp_netbios_name(), 0x0, + source_name, + dgram->source_name.name_type, + p->ip, ip, p->port); + break; + } + + case LOGON_PRIMARY_QUERY: { + + struct nbt_netlogon_response_from_pdc get_pdc; + + if (!lp_domain_master()) { + /* We're not Primary Domain Controller -- ignore this */ + return; + } + + DEBUG(5,("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", + request.req.pdc.computer_name, + inet_ntoa(p->ip), + lp_netbios_name(), + lp_workgroup(), + NETLOGON_RESPONSE_FROM_PDC, + request.req.pdc.nt_version, + request.req.pdc.lmnt_token, + request.req.pdc.lm20_token)); + + get_pdc.command = NETLOGON_RESPONSE_FROM_PDC; + get_pdc.pdc_name = lp_netbios_name(); + get_pdc._pad = data_blob_null; + get_pdc.unicode_pdc_name = lp_netbios_name(); + get_pdc.domain_name = lp_workgroup(); + get_pdc.nt_version = NETLOGON_NT_VERSION_1; + get_pdc.lmnt_token = 0xffff; + get_pdc.lm20_token = 0xffff; + + response.response_type = NETLOGON_GET_PDC; + response.data.get_pdc = get_pdc; + + status = push_nbt_netlogon_response(&blob_out, talloc_tos(), &response); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("process_logon_packet: failed to push packet\n")); + return; + } + + if (DEBUGLEVEL >= 10) { + NDR_PRINT_DEBUG(nbt_netlogon_response_from_pdc, &response.data.get_pdc); + } + + send_mailslot(True, request.req.pdc.mailslot_name, + (char *)blob_out.data, + blob_out.length, + lp_netbios_name(), 0x0, + source_name, + dgram->source_name.name_type, + p->ip, ip, p->port); + + return; + } + + case LOGON_SAM_LOGON_REQUEST: { + char *source_addr; + bool user_unknown = false; + + struct netlogon_samlogon_response samlogon; + struct NETLOGON_SAM_LOGON_RESPONSE_NT40 nt4; + + if (global_nmbd_proxy_logon) { + nmbd_proxy_logon(global_nmbd_proxy_logon, + ip, p, (const uint8_t *)buf, len); + return; + } + + source_addr = SMB_STRDUP(inet_ntoa(dgram->header.source_ip)); + if (source_addr == NULL) { + DEBUG(3, ("out of memory copying client" + " address string\n")); + return; + } + + DEBUG(5,("process_logon_packet: LOGON_SAM_LOGON_REQUEST request from %s(%s) for %s, returning logon svr %s domain %s code %x token=%x\n", + request.req.logon.computer_name, + inet_ntoa(p->ip), + request.req.logon.user_name, + pdc_name, + lp_workgroup(), + LOGON_SAM_LOGON_RESPONSE, + request.req.logon.lmnt_token)); + + if (!request.req.logon.user_name) { + user_unknown = true; + } + + nt4.command = user_unknown ? LOGON_SAM_LOGON_USER_UNKNOWN : + LOGON_SAM_LOGON_RESPONSE; + nt4.pdc_name = pdc_name; + nt4.user_name = request.req.logon.user_name; + nt4.domain_name = lp_workgroup(); + nt4.nt_version = NETLOGON_NT_VERSION_1; + nt4.lmnt_token = 0xffff; + nt4.lm20_token = 0xffff; + + samlogon.ntver = NETLOGON_NT_VERSION_1; + samlogon.data.nt4 = nt4; + + if (DEBUGLEVEL >= 10) { + NDR_PRINT_DEBUG(NETLOGON_SAM_LOGON_RESPONSE_NT40, &nt4); + } + + response.response_type = NETLOGON_SAMLOGON; + response.data.samlogon = samlogon; + + status = push_nbt_netlogon_response(&blob_out, talloc_tos(), &response); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("process_logon_packet: failed to push packet\n")); + SAFE_FREE(source_addr); + return; + } + + /* + * handle delay. + * packets requeued after delay are marked as + * locked. + */ + if ((p->locked == False) && + (strlen(request.req.logon.user_name) == 0) && + delay_logon(source_name, source_addr)) + { + struct timeval when; + + DEBUG(3, ("process_logon_packet: " + "delaying initial logon " + "reply for client %s(%s) for " + "%u milliseconds\n", + source_name, source_addr, + lp_init_logon_delay())); + + when = timeval_current_ofs_msec(lp_init_logon_delay()); + p->locked = true; + tevent_add_timer(nmbd_event_context(), + NULL, + when, + delayed_init_logon_handler, + p); + } else { + DEBUG(3, ("process_logon_packet: " + "processing delayed initial " + "logon reply for client " + "%s(%s)\n", + source_name, source_addr)); + p->locked = false; + send_mailslot(true, request.req.logon.mailslot_name, + (char *)blob_out.data, + blob_out.length, + lp_netbios_name(), 0x0, + source_name, + dgram->source_name.name_type, + p->ip, ip, p->port); + } + + SAFE_FREE(source_addr); + + break; + } + + /* Announce change to UAS or SAM. Send by the domain controller when a + replication event is required. */ + + case NETLOGON_ANNOUNCE_UAS: + DEBUG(5, ("Got NETLOGON_ANNOUNCE_UAS\n")); + break; + + default: + DEBUG(3,("process_logon_packet: Unknown domain request %d\n", + request.command)); + return; + } +} diff --git a/source3/nmbd/nmbd_proto.h b/source3/nmbd/nmbd_proto.h new file mode 100644 index 0000000..4cfb589 --- /dev/null +++ b/source3/nmbd/nmbd_proto.h @@ -0,0 +1,385 @@ +/* + * Unix SMB/CIFS implementation. + * NBT netbios routines and daemon - version 2 + * + * Copyright (C) Andrew Tridgell 1994-1998 + * Copyright (C) Jeremy Allison 1994-2005 + * Copyright (C) Luke Kenneth Casson Leighton 1994-1998 + * Copyright (C) John H Terpstra 1995-1998 + * Copyright (C) Christopher R. Hertel 1998 + * Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002 + * Copyright (C) Jelmer Vernooij 2002,2003 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* The following definitions come from nmbd/asyncdns.c */ + +int asyncdns_fd(void); +void kill_async_dns_child(void); +void start_async_dns(struct messaging_context *msg); +void run_dns_queue(struct messaging_context *msg); +bool queue_dns_query(struct packet_struct *p,struct nmb_name *question); +bool queue_dns_query(struct packet_struct *p,struct nmb_name *question); +void kill_async_dns_child(void); + +/* The following definitions come from nmbd/nmbd.c */ + +struct tevent_context *nmbd_event_context(void); + +/* The following definitions come from nmbd/nmbd_become_dmb.c */ + +void add_domain_names(time_t t); + +/* The following definitions come from nmbd/nmbd_become_lmb.c */ + +void insert_permanent_name_into_unicast( struct subnet_record *subrec, + struct nmb_name *nmbname, uint16_t nb_type ); +void unbecome_local_master_browser(struct subnet_record *subrec, struct work_record *work, + bool force_new_election); +void become_local_master_browser(struct subnet_record *subrec, struct work_record *work); +void set_workgroup_local_master_browser_name( struct work_record *work, const char *newname); + +/* The following definitions come from nmbd/nmbd_browserdb.c */ + +void update_browser_death_time( struct browse_cache_record *browc ); +struct browse_cache_record *create_browser_in_lmb_cache( const char *work_name, + const char *browser_name, + struct in_addr ip ); +struct browse_cache_record *find_browser_in_lmb_cache( const char *browser_name ); +void expire_lmb_browsers( time_t t ); + +/* The following definitions come from nmbd/nmbd_browsesync.c */ + +void dmb_expire_and_sync_browser_lists(time_t t); +void announce_and_sync_with_domain_master_browser( struct subnet_record *subrec, + struct work_record *work); +void collect_all_workgroup_names_from_wins_server(time_t t); +void sync_all_dmbs(time_t t); + +/* The following definitions come from nmbd/nmbd_elections.c */ + +void check_master_browser_exists(time_t t); +void run_elections(time_t t); +void process_election(struct subnet_record *subrec, struct packet_struct *p, const char *buf); +bool check_elections(void); +void nmbd_message_election(struct messaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data); + +/* The following definitions come from nmbd/nmbd_incomingdgrams.c */ + +void tell_become_backup(void); +void process_host_announce(struct subnet_record *subrec, struct packet_struct *p, const char *buf); +void process_workgroup_announce(struct subnet_record *subrec, struct packet_struct *p, const char *buf); +void process_local_master_announce(struct subnet_record *subrec, struct packet_struct *p, const char *buf); +void process_master_browser_announce(struct subnet_record *subrec, + struct packet_struct *p,const char *buf); +void process_lm_host_announce(struct subnet_record *subrec, struct packet_struct *p, const char *buf, int len); +void process_get_backup_list_request(struct subnet_record *subrec, + struct packet_struct *p,const char *buf); +void process_reset_browser(struct subnet_record *subrec, + struct packet_struct *p,const char *buf); +void process_announce_request(struct subnet_record *subrec, struct packet_struct *p, const char *buf); +void process_lm_announce_request(struct subnet_record *subrec, struct packet_struct *p, const char *buf, int len); + +/* The following definitions come from nmbd/nmbd_incomingrequests.c */ + +void process_name_release_request(struct subnet_record *subrec, + struct packet_struct *p); +void process_name_refresh_request(struct subnet_record *subrec, + struct packet_struct *p); +void process_name_registration_request(struct subnet_record *subrec, + struct packet_struct *p); +void process_node_status_request(struct subnet_record *subrec, struct packet_struct *p); +void process_name_query_request(struct subnet_record *subrec, struct packet_struct *p); + +/* The following definitions come from nmbd/nmbd_lmhosts.c */ + +void load_lmhosts_file(const char *fname); +bool find_name_in_lmhosts(struct nmb_name *nmbname, struct name_record **namerecp); + +/* The following definitions come from nmbd/nmbd_logonnames.c */ + +void add_logon_names(void); + +/* The following definitions come from nmbd/nmbd_mynames.c */ + +bool nmbd_init_my_netbios_names(void); +const char *my_netbios_names(int i); +void register_my_workgroup_one_subnet(struct subnet_record *subrec); +bool register_my_workgroup_and_names(void); +void release_wins_names(void); +void refresh_my_names(time_t t); + +/* The following definitions come from nmbd/nmbd_namelistdb.c */ + +void set_samba_nb_type(void); +void remove_name_from_namelist(struct subnet_record *subrec, + struct name_record *namerec ); +struct name_record *find_name_on_subnet(struct subnet_record *subrec, + const struct nmb_name *nmbname, + bool self_only); +struct name_record *find_name_for_remote_broadcast_subnet(struct nmb_name *nmbname, + bool self_only); +void update_name_ttl( struct name_record *namerec, int ttl ); +bool add_name_to_subnet( struct subnet_record *subrec, + const char *name, + int type, + uint16_t nb_flags, + int ttl, + enum name_source source, + int num_ips, + struct in_addr *iplist); +void standard_success_register(struct subnet_record *subrec, + struct userdata_struct *userdata, + struct nmb_name *nmbname, uint16_t nb_flags, int ttl, + struct in_addr registered_ip); +void standard_fail_register( struct subnet_record *subrec, + struct nmb_name *nmbname ); +bool find_ip_in_name_record( struct name_record *namerec, struct in_addr ip ); +void add_ip_to_name_record( struct name_record *namerec, struct in_addr new_ip ); +void remove_ip_from_name_record( struct name_record *namerec, + struct in_addr remove_ip ); +void standard_success_release( struct subnet_record *subrec, + struct userdata_struct *userdata, + struct nmb_name *nmbname, + struct in_addr released_ip ); +void expire_names(time_t t); +void add_samba_names_to_subnet( struct subnet_record *subrec ); +void dump_name_record(struct name_record *namerec, FILE *fp); +void dump_all_namelists(void); + +/* The following definitions come from nmbd/nmbd_namequery.c */ + +bool query_name(struct subnet_record *subrec, const char *name, int type, + query_name_success_function success_fn, + query_name_fail_function fail_fn, + struct userdata_struct *userdata); +bool query_name_from_wins_server(struct in_addr ip_to, + const char *name, int type, + query_name_success_function success_fn, + query_name_fail_function fail_fn, + struct userdata_struct *userdata); + +/* The following definitions come from nmbd/nmbd_nameregister.c */ + +void register_name(struct subnet_record *subrec, + const char *name, int type, uint16_t nb_flags, + register_name_success_function success_fn, + register_name_fail_function fail_fn, + struct userdata_struct *userdata); +void wins_refresh_name(struct name_record *namerec); + +/* The following definitions come from nmbd/nmbd_namerelease.c */ + +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); + +/* The following definitions come from nmbd/nmbd_nodestatus.c */ + +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); + +/* The following definitions come from nmbd/nmbd_packets.c */ + +bool nmbd_init_packet_server(void); + +uint16_t get_nb_flags(char *buf); +void set_nb_flags(char *buf, uint16_t nb_flags); +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_t nb_flags); +void queue_wins_refresh(struct nmb_name *nmbname, + response_function resp_fn, + timeout_response_function timeout_fn, + uint16_t nb_flags, + struct in_addr refresh_ip, + const char *tag); +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_t nb_flags, + struct in_addr register_ip, + struct in_addr wins_ip); +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_t nb_flags, + struct in_addr release_ip, + struct in_addr dest_ip); +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 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 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); +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); +void queue_packet(struct packet_struct *packet); +void run_packet_queue(void); +void retransmit_or_expire_response_records(time_t t); +bool listen_for_packets(struct messaging_context *msg, bool run_election); +bool send_mailslot(bool unique, const char *mailslot,char *buf, size_t 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); + +/* The following definitions come from nmbd/nmbd_processlogon.c */ + +bool initialize_nmbd_proxy_logon(void); + +void process_logon_packet(struct packet_struct *p, const char *buf,int len, + const char *mailslot); + +/* The following definitions come from nmbd/nmbd_responserecordsdb.c */ + +void remove_response_record(struct subnet_record *subrec, + struct response_record *rrec); +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 *find_response_record(struct subnet_record **ppsubrec, + uint16_t id); +bool is_refresh_already_queued(struct subnet_record *subrec, struct name_record *namerec); + +/* The following definitions come from nmbd/nmbd_sendannounce.c */ + +void send_browser_reset(int reset_type, const char *to_name, int to_type, struct in_addr to_ip); +void broadcast_announce_request(struct subnet_record *subrec, struct work_record *work); +void announce_my_server_names(time_t t); +void announce_my_lm_server_names(time_t t); +void reset_announce_timer(void); +void announce_myself_to_domain_master_browser(time_t t); +void announce_my_servers_removed(void); +void announce_remote(time_t t); +void browse_sync_remote(time_t t); + +/* The following definitions come from nmbd/nmbd_serverlistdb.c */ + +void remove_all_servers(struct work_record *work); +struct server_record *find_server_in_workgroup(struct work_record *work, const char *name); +void remove_server_from_workgroup(struct work_record *work, struct server_record *servrec); +struct server_record *create_server_on_workgroup(struct work_record *work, + const char *name,int servertype, + int ttl, const char *comment); +void update_server_ttl(struct server_record *servrec, int ttl); +void expire_servers(struct work_record *work, time_t t); +void write_browse_list_entry(FILE *fp, const char *name, uint32_t rec_type, + const char *local_master_browser_name, const char *description); +void write_browse_list(time_t t, bool force_write); + +/* The following definitions come from nmbd/nmbd_subnetdb.c */ + +void close_subnet(struct subnet_record *subrec); +struct subnet_record *make_normal_subnet(const struct interface *iface); +bool create_subnets(void); +bool we_are_a_wins_client(void); +struct subnet_record *get_next_subnet_maybe_unicast(struct subnet_record *subrec); +struct subnet_record *get_next_subnet_maybe_unicast_or_wins_server(struct subnet_record *subrec); + +/* The following definitions come from nmbd/nmbd_synclists.c */ + +void sync_browse_lists(struct work_record *work, + char *name, int nm_type, + struct in_addr ip, bool local, bool servers); +void sync_check_completion(void); + +/* The following definitions come from nmbd/nmbd_winsproxy.c */ + +void make_wins_proxy_name_query_request( struct subnet_record *subrec, + struct packet_struct *incoming_packet, + struct nmb_name *question_name); + +/* The following definitions come from nmbd/nmbd_winsserver.c */ + +struct name_record *find_name_on_wins_subnet(const struct nmb_name *nmbname, bool self_only); +bool wins_store_changed_namerec(const struct name_record *namerec); +bool add_name_to_wins_subnet(const struct name_record *namerec); +bool remove_name_from_wins_namelist(struct name_record *namerec); +void dump_wins_subnet_namelist(FILE *fp); +bool packet_is_for_wins_server(struct packet_struct *packet); +bool initialise_wins(void); +void wins_process_name_refresh_request( struct subnet_record *subrec, + struct packet_struct *p ); +void wins_process_name_registration_request(struct subnet_record *subrec, + struct packet_struct *p); +void wins_process_multihomed_name_registration_request( struct subnet_record *subrec, + struct packet_struct *p); +void fetch_all_active_wins_1b_names(void); +void send_wins_name_query_response(int rcode, struct packet_struct *p, + struct name_record *namerec); +void wins_process_name_query_request(struct subnet_record *subrec, + struct packet_struct *p); +void wins_process_name_release_request(struct subnet_record *subrec, + struct packet_struct *p); +void initiate_wins_processing(time_t t); +void wins_write_name_record(struct name_record *namerec, FILE *fp); +void wins_write_database(time_t t, bool background); +void nmbd_wins_new_entry(struct messaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data); + +/* The following definitions come from nmbd/nmbd_workgroupdb.c */ + +struct work_record *find_workgroup_on_subnet(struct subnet_record *subrec, + const char *name); +struct work_record *create_workgroup_on_subnet(struct subnet_record *subrec, + const char *name, int ttl); +void update_workgroup_ttl(struct work_record *work, int ttl); +void initiate_myworkgroup_startup(struct subnet_record *subrec, struct work_record *work); +void dump_workgroups(bool force_write); +void expire_workgroups_and_servers(time_t t); diff --git a/source3/nmbd/nmbd_responserecordsdb.c b/source3/nmbd/nmbd_responserecordsdb.c new file mode 100644 index 0000000..8876722 --- /dev/null +++ b/source3/nmbd/nmbd_responserecordsdb.c @@ -0,0 +1,244 @@ +/* + 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 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "nmbd/nmbd.h" + +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) +{ + 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)); + + DLIST_ADD_END(subrec->responselist, rrec); +} + +/*************************************************************************** + Remove an expected response record from the list + **************************************************************************/ + +void remove_response_record(struct subnet_record *subrec, + struct response_record *rrec) +{ + /* It is possible this can be called twice, + with a rrec pointer that has been freed. So + before we indirect into rrec, search for it + on the responselist first. Bug #3617. JRA. */ + + struct response_record *p = NULL; + + for (p = subrec->responselist; p; p = p->next) { + if (p == rrec) { + break; + } + } + + if (p == NULL) { + /* We didn't find rrec on the list. */ + return; + } + + DLIST_REMOVE(subrec->responselist, rrec); + + 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 = SMB_MALLOC_P(struct response_record))) { + 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 *) + SMB_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_t 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_t 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(3,("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/source3/nmbd/nmbd_sendannounce.c b/source3/nmbd/nmbd_sendannounce.c new file mode 100644 index 0000000..4e8be04 --- /dev/null +++ b/source3/nmbd/nmbd_sendannounce.c @@ -0,0 +1,602 @@ +/* + 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 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "../librpc/gen_ndr/svcctl.h" +#include "nmbd/nmbd.h" +#include "lib/util/string_wrappers.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) +{ + char outbuf[1024]; + char *p; + + DBG_NOTICE("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) +{ + char outbuf[1024]; + char *p; + + work->needannounce = True; + + DBG_NOTICE("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_check(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) +{ + char outbuf[1024]; + unstring upper_server_name; + 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. */ + + strlcpy(upper_server_name, server_name ? server_name : "", sizeof(upper_server_name)); + if (!strupper_m(upper_server_name)) { + DBG_WARNING("strupper_m %s failed\n", upper_server_name); + return; + } + push_string_check(p+5, upper_server_name, 16, STR_ASCII|STR_TERMINATE); + + SCVAL(p,21,SAMBA_MAJOR_NBT_ANNOUNCE_VERSION); /* Major version. */ + SCVAL(p,22,SAMBA_MINOR_NBT_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_check(p+31, server_comment, sizeof(outbuf) - (p + 31 - outbuf), 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) +{ + char outbuf[1024]; + 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,SAMBA_MAJOR_NBT_ANNOUNCE_VERSION); /* Major version. */ + SCVAL(p,7,SAMBA_MINOR_NBT_ANNOUNCE_VERSION); /* Minor version. */ + SSVAL(p,8,announce_interval); /* In seconds - according to spec. */ + + p += 10; + p += push_string_check(p, server_name, 15, STR_ASCII|STR_UPPER|STR_TERMINATE); + p += push_string_check(p, server_comment, sizeof(outbuf)- (p - outbuf), 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_t type = servrec->serv.type & ~SV_TYPE_LOCAL_LIST_ONLY; + + DBG_NOTICE("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) +{ + DBG_NOTICE("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_t type = servrec->serv.type & ~SV_TYPE_LOCAL_LIST_ONLY; + + DBG_NOTICE("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_t type = servrec->serv.type & ~SV_TYPE_LOCAL_LIST_ONLY; + + DBG_NOTICE("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 (fstring not netbios name struct). */ + 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 && + (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()) { + DBG_DEBUG("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)) { + DBG_DEBUG("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)) { + DBG_NOTICE( "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 = NULL; + const char *ptr; + static time_t last_time = 0; + char *s2; + struct in_addr addr; + char *comment; + int stype = lp_default_server_announce(); + TALLOC_CTX *frame = NULL; + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + + if (last_time && (t < (last_time + REMOTE_ANNOUNCE_INTERVAL))) + return; + + last_time = t; + + s = lp_remote_announce(talloc_tos(), lp_sub); + if (!*s) + return; + + comment = string_truncate(lp_server_string(talloc_tos(), lp_sub), + MAX_SERVER_STRING_LENGTH); + + frame = talloc_stackframe(); + for (ptr=s; next_token_talloc(frame,&ptr,&s2,NULL); ) { + /* 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); + + DBG_INFO("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); + } + } + TALLOC_FREE(frame); +} + +/**************************************************************************** + 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; + char *s2; + struct in_addr addr; + struct work_record *work; + char outbuf[1024]; + char *p; + unstring myname; + TALLOC_CTX *frame = NULL; + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + + if (last_time && (t < (last_time + REMOTE_ANNOUNCE_INTERVAL))) + return; + + last_time = t; + + s = lp_remote_browse_sync(talloc_tos(), lp_sub); + 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) { + DBG_WARNING("browse_sync_remote: Cannot find workgroup %s on subnet %s\n", + lp_workgroup(), FIRST_SUBNET->subnet_name ); + return; + } + + if(!AM_LOCAL_MASTER_BROWSER(work)) { + DBG_NOTICE("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++; + + unstrcpy(myname, lp_netbios_name()); + if (!strupper_m(myname)) { + DBG_WARNING("strupper_m %s failed\n", myname); + return; + } + myname[15]='\0'; + push_ascii(p, myname, sizeof(outbuf)-PTR_DIFF(p,outbuf)-1, STR_TERMINATE); + + p = skip_string(outbuf,sizeof(outbuf),p); + + frame = talloc_stackframe(); + for (ptr=s; next_token_talloc(frame,&ptr,&s2,NULL); ) { + /* The entries are of the form a.b.c.d */ + addr = interpret_addr2(s2); + + DBG_INFO("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); + } + TALLOC_FREE(frame); +} diff --git a/source3/nmbd/nmbd_serverlistdb.c b/source3/nmbd/nmbd_serverlistdb.c new file mode 100644 index 0000000..2aced3d --- /dev/null +++ b/source3/nmbd/nmbd_serverlistdb.c @@ -0,0 +1,417 @@ +/* + 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 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "system/filesys.h" +#include "../librpc/gen_ndr/svcctl.h" +#include "nmbd/nmbd.h" +#include "lib/util/string_wrappers.h" + +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; + DLIST_REMOVE(work->serverlist, servrec); + ZERO_STRUCTP(servrec); + SAFE_FREE(servrec); + } + + work->subnet->work_changed = True; +} + +/*************************************************************************** + Add a server into a workgroup serverlist. + **************************************************************************/ + +static void add_server_to_workgroup(struct work_record *work, + struct server_record *servrec) +{ + DLIST_ADD_END(work->serverlist, servrec); + 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) +{ + DLIST_REMOVE(work->serverlist, servrec); + 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(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 = SMB_MALLOC_P(struct server_record)) == NULL) { + DEBUG(0,("create_server_entry_on_workgroup: malloc fail !\n")); + return NULL; + } + + memset((char *)servrec,'\0',sizeof(*servrec)); + + servrec->subnet = work->subnet; + + fstrcpy(servrec->serv.name,name); + fstrcpy(servrec->serv.comment,comment); + if (!strupper_m(servrec->serv.name)) { + DEBUG(2,("strupper_m %s failed\n", servrec->serv.name)); + SAFE_FREE(servrec); + return NULL; + } + 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)); + + 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; +} + +/******************************************************************* + 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); + } + } +} + +/******************************************************************* + 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_t 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 && (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_t 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(FILE *fp, const char *name, uint32_t rec_type, + const char *local_master_browser_name, const char *description) +{ + fstring tmp; + + slprintf(tmp,sizeof(tmp)-1, "\"%s\"", name); + fprintf(fp, "%-25s ", tmp); + fprintf(fp, "%08x ", rec_type); + slprintf(tmp, sizeof(tmp)-1, "\"%s\" ", local_master_browser_name); + fprintf(fp, "%-30s", tmp); + 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; + char *fname; + char *fnamenew; + uint32_t stype; + int i; + int fd; + FILE *fp; + bool list_changed = force_write; + static time_t lasttime = 0; + TALLOC_CTX *ctx = talloc_tos(); + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + + /* 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++; + + fname = cache_path(talloc_tos(), SERVER_LIST); + if (!fname) { + return; + } + fnamenew = talloc_asprintf(ctx, "%s.", + fname); + if (!fnamenew) { + talloc_free(fname); + return; + } + + fd = open(fnamenew, O_WRONLY|O_CREAT|O_TRUNC, 0644); + if (fd == -1) { + DBG_ERR("Can't open file %s: %s\n", fnamenew, + strerror(errno)); + talloc_free(fnamenew); + talloc_free(fname); + return; + } + + fp = fdopen(fd, "w"); + if (!fp) { + DBG_ERR("fdopen failed: %s\n", strerror(errno)); + close(fd); + talloc_free(fnamenew); + talloc_free(fname); + return; + } + fd = -1; + + /* + * 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())); + fclose(fp); + talloc_free(fnamenew); + talloc_free(fname); + 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_server_string(talloc_tos(), lp_sub), 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_t 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_t 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); + } + } + } + } + + fclose(fp); + unlink(fname); + chmod(fnamenew,0644); + rename(fnamenew,fname); + DEBUG(3,("write_browse_list: Wrote browse list into file %s\n",fname)); + talloc_free(fnamenew); + talloc_free(fname); +} diff --git a/source3/nmbd/nmbd_subnetdb.c b/source3/nmbd/nmbd_subnetdb.c new file mode 100644 index 0000000..b2bcedd --- /dev/null +++ b/source3/nmbd/nmbd_subnetdb.c @@ -0,0 +1,436 @@ +/* + 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 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + + Revision History: + +*/ + +#include "includes.h" +#include "nmbd/nmbd.h" + +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_t 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); +} + +/**************************************************************************** +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) +{ + if (subrec->nmb_sock != -1) { + close(subrec->nmb_sock); + subrec->nmb_sock = -1; + } + if (subrec->nmb_bcast != -1) { + close(subrec->nmb_bcast); + subrec->nmb_bcast = -1; + } + if (subrec->dgram_sock != -1) { + close(subrec->dgram_sock); + subrec->dgram_sock = -1; + } + if (subrec->dgram_bcast != -1) { + close(subrec->dgram_bcast); + subrec->dgram_bcast = -1; + } + + DLIST_REMOVE(subnetlist, subrec); +} + +/**************************************************************************** + 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 = -1; + int dgram_sock = -1; + int nmb_bcast = -1; + int dgram_bcast = -1; + bool bind_bcast = lp_nmbd_bind_explicit_broadcast(); + + /* Check if we are creating a non broadcast subnet - if so don't create + sockets. */ + + if (type == NORMAL_SUBNET) { + struct sockaddr_storage ss; + struct sockaddr_storage ss_bcast; + + in_addr_to_sockaddr_storage(&ss, myip); + in_addr_to_sockaddr_storage(&ss_bcast, bcast_ip); + + /* + * Attempt to open the sockets on port 137/138 for this interface + * and bind them. + * Fail the subnet creation if this fails. + */ + + nmb_sock = open_socket_in( + SOCK_DGRAM, &ss, global_nmb_port, true); + if (nmb_sock < 0) { + DBG_ERR("Failed to open nmb socket on interface %s " + "for port %d: %s\n", + inet_ntoa(myip), + global_nmb_port, + strerror(-nmb_sock)); + goto failed; + } + set_socket_options(nmb_sock,"SO_BROADCAST"); + set_blocking(nmb_sock, false); + + if (bind_bcast) { + nmb_bcast = open_socket_in( + SOCK_DGRAM, &ss_bcast, global_nmb_port, true); + if (nmb_bcast < 0) { + DBG_ERR("Failed to open nmb bcast socket on " + "interface %s for port %d: %s\n", + inet_ntoa(myip), + global_nmb_port, + strerror(-nmb_bcast)); + goto failed; + } + set_socket_options(nmb_bcast, "SO_BROADCAST"); + set_blocking(nmb_bcast, false); + } + + dgram_sock = open_socket_in(SOCK_DGRAM, &ss, DGRAM_PORT, true); + if (dgram_sock < 0) { + DBG_ERR("Failed to open dgram socket on " + "interface %s for port %d: %s\n", + inet_ntoa(myip), + DGRAM_PORT, + strerror(-dgram_sock)); + goto failed; + } + set_socket_options(dgram_sock, "SO_BROADCAST"); + set_blocking(dgram_sock, false); + + if (bind_bcast) { + dgram_bcast = open_socket_in( + SOCK_DGRAM, &ss_bcast, DGRAM_PORT, true); + if (dgram_bcast < 0) { + DBG_ERR("Failed to open dgram bcast socket on " + "interface %s for port %d: %s\n", + inet_ntoa(myip), + DGRAM_PORT, + strerror(-dgram_bcast)); + goto failed; + } + set_socket_options(dgram_bcast, "SO_BROADCAST"); + set_blocking(dgram_bcast, false); + } + } + + subrec = SMB_MALLOC_P(struct subnet_record); + if (!subrec) { + DEBUG(0,("make_subnet: malloc fail !\n")); + goto failed; + } + + ZERO_STRUCTP(subrec); + + if((subrec->subnet_name = SMB_STRDUP(name)) == NULL) { + DEBUG(0,("make_subnet: malloc fail for subnet name !\n")); + goto failed; + } + + 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->nmb_bcast = nmb_bcast; + subrec->dgram_sock = dgram_sock; + subrec->dgram_bcast = dgram_bcast; + + return subrec; + +failed: + SAFE_FREE(subrec); + if (nmb_sock >= 0) { + close(nmb_sock); + } + if (nmb_bcast >= 0) { + close(nmb_bcast); + } + if (dgram_sock >= 0) { + close(dgram_sock); + } + if (dgram_bcast >= 0) { + close(dgram_bcast); + } + return NULL; +} + +/**************************************************************************** + Create a normal subnet +**************************************************************************/ + +struct subnet_record *make_normal_subnet(const struct interface *iface) +{ + + struct subnet_record *subrec; + const struct in_addr *pip = &((const struct sockaddr_in *)&iface->ip)->sin_addr; + const struct in_addr *pbcast = &((const struct sockaddr_in *)&iface->bcast)->sin_addr; + const struct in_addr *pnmask = &((const struct sockaddr_in *)&iface->netmask)->sin_addr; + + subrec = make_subnet(inet_ntoa(*pip), NORMAL_SUBNET, + *pip, *pbcast, *pnmask); + if (subrec) { + add_subnet(subrec); + } + return subrec; +} + +/**************************************************************************** + Create subnet entries. +**************************************************************************/ + +bool create_subnets(void) +{ + /* We only count IPv4 interfaces whilst we're waiting. */ + int num_interfaces; + int i; + struct in_addr unicast_ip, ipzero; + + try_interfaces_again: + + /* Only count IPv4, non-loopback interfaces. */ + if (iface_count_v4_nl() == 0) { + daemon_status("nmbd", + "No local IPv4 non-loopback interfaces " + "available, waiting for interface ..."); + DEBUG(0,("NOTE: NetBIOS name resolution is not supported for " + "Internet Protocol Version 6 (IPv6).\n")); + } + + /* We only count IPv4, non-loopback interfaces here. */ + while (iface_count_v4_nl() == 0) { + void (*saved_handler)(int); + + /* + * Whilst we're waiting for an interface, allow SIGTERM to + * cause us to exit. + */ + + saved_handler = CatchSignal(SIGTERM, SIG_DFL); + + usleep(NMBD_WAIT_INTERFACES_TIME_USEC); + load_interfaces(); + + /* + * We got an interface, restore our normal term handler. + */ + + CatchSignal(SIGTERM, saved_handler); + } + + /* + * Here we count v4 and v6 - we know there's at least one + * IPv4 interface and we filter on it below. + */ + 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++) { + const struct interface *iface = get_interface(i); + + if (!iface) { + DEBUG(2,("create_subnets: can't get interface %d.\n", i )); + continue; + } + + /* Ensure we're only dealing with IPv4 here. */ + if (iface->ip.ss_family != AF_INET) { + DEBUG(2,("create_subnets: " + "ignoring non IPv4 interface.\n")); + continue; + } + + /* + * 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 (is_loopback_addr((const struct sockaddr *)&iface->ip)) { + DEBUG(2,("create_subnets: Ignoring loopback interface.\n" )); + continue; + } + + if (!make_normal_subnet(iface)) + return False; + } + + /* We must have at least one subnet. */ + if (subnetlist == NULL) { + void (*saved_handler)(int); + + DEBUG(0,("create_subnets: Unable to create any subnet from " + "given interfaces. Is your interface line in " + "smb.conf correct ?\n")); + + saved_handler = CatchSignal(SIGTERM, SIG_DFL); + + usleep(NMBD_WAIT_INTERFACES_TIME_USEC); + load_interfaces(); + + CatchSignal(SIGTERM, saved_handler); + goto try_interfaces_again; + } + + if (lp_we_are_a_wins_server()) { + /* Pick the first interface IPv4 address as the WINS server + * ip. */ + const struct in_addr *nip = first_ipv4_iface(); + + if (!nip) { + return False; + } + + unicast_ip = *nip; + } 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_v4(&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_v4(&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/source3/nmbd/nmbd_synclists.c b/source3/nmbd/nmbd_synclists.c new file mode 100644 index 0000000..0f5c42f --- /dev/null +++ b/source3/nmbd/nmbd_synclists.c @@ -0,0 +1,325 @@ +/* + 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 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* 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" +#include "system/filesys.h" +#include "../librpc/gen_ndr/svcctl.h" +#include "nmbd/nmbd.h" +#include "libsmb/libsmb.h" +#include "libsmb/clirap.h" +#include "../libcli/smb/smbXcli_base.h" +#include "lib/util/string_wrappers.h" +#include "source3/lib/substitute.h" + +struct sync_record { + struct sync_record *next, *prev; + unstring workgroup; + unstring server; + char *fname; + struct in_addr ip; + pid_t pid; +}; + +/* a linked list of current sync connections */ +static struct sync_record *syncs; + +static FILE *fp; + +/******************************************************************* + This is the NetServerEnum callback. + Note sname and comment are in UNIX codepage format. + ******************************************************************/ + +static void callback(const char *sname, uint32_t stype, + const char *comment, void *state) +{ + 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) +{ + fstring unix_workgroup; + struct cli_state *cli; + uint32_t local_type = local ? SV_TYPE_LOCAL_LIST_ONLY : 0; + struct sockaddr_storage ss; + NTSTATUS status; + + /* W2K DMB's return empty browse lists on port 445. Use 139. + * Patch from Andy Levine andyl@epicrealm.com. + */ + + in_addr_to_sockaddr_storage(&ss, ip); + + status = cli_connect_nb(name, &ss, NBT_SMB_PORT, nm_type, + get_local_machine_name(), SMB_SIGNING_DEFAULT, + 0, &cli); + if (!NT_STATUS_IS_OK(status)) { + return; + } + + status = smbXcli_negprot(cli->conn, + cli->timeout, + PROTOCOL_CORE, + PROTOCOL_NT1, + NULL, + NULL, + NULL); + if (!NT_STATUS_IS_OK(status)) { + cli_shutdown(cli); + return; + } + + status = cli_session_setup_anon(cli); + if (!NT_STATUS_IS_OK(status)) { + cli_shutdown(cli); + return; + } + + if (!NT_STATUS_IS_OK(cli_tree_connect(cli, "IPC$", "IPC", NULL))) { + 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; + int fd; + + /* Check we're not trying to sync with ourselves. This can + happen if we are a domain *and* a local master browser. */ + if (ismyip_v4(ip)) { +done: + return; + } + + s = SMB_MALLOC_P(struct sync_record); + if (!s) goto done; + + ZERO_STRUCTP(s); + + unstrcpy(s->workgroup, work->work_group); + unstrcpy(s->server, name); + s->ip = ip; + + if (asprintf(&s->fname, "%s/sync.%d", lp_lock_directory(), counter++) < 0) { + SAFE_FREE(s); + goto done; + } + /* Safe to use as 0 means no size change. */ + all_string_sub(s->fname,"//", "/", 0); + + DLIST_ADD(syncs, s); + + /* the parent forks and returns, leaving the child to do the + actual sync */ + CatchChild(); + if ((s->pid = fork())) return; + + BlockSignals( False, SIGTERM ); + + DEBUG(2,("Initiating browse sync for %s to %s(%s)\n", + work->work_group, name, inet_ntoa(ip))); + + fd = open(s->fname, O_WRONLY|O_CREAT|O_TRUNC, 0644); + if (fd == -1) { + _exit(1); + } + + fp = fdopen(fd, "w"); + if (!fp) { + _exit(1); + } + fd = -1; + + sync_child(name, nm_type, work->work_group, ip, local, servers, + s->fname); + + fclose(fp); + _exit(0); +} + +/********************************************************************** + Handle one line from a completed sync file. + **********************************************************************/ + +static void complete_one(struct sync_record *s, + char *sname, uint32_t 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 */ + unstrcpy(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) +{ + FILE *f; + char *server; + char *type_str; + unsigned type; + char *comment; + char line[1024]; + const char *ptr; + int count=0; + + f = fopen(s->fname, "r"); + + if (!f) + return; + + while (!feof(f)) { + TALLOC_CTX *frame = NULL; + + if (!fgets_slash(NULL, line, sizeof(line), f)) + continue; + + ptr = line; + + frame = talloc_stackframe(); + if (!next_token_talloc(frame,&ptr,&server,NULL) || + !next_token_talloc(frame,&ptr,&type_str,NULL) || + !next_token_talloc(frame,&ptr,&comment,NULL)) { + TALLOC_FREE(frame); + continue; + } + + sscanf(type_str, "%X", &type); + + complete_one(s, server, type, comment); + + count++; + TALLOC_FREE(frame); + } + 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_by_pid(s->pid)) { + /* it has completed - grab the info */ + complete_sync(s); + DLIST_REMOVE(syncs, s); + SAFE_FREE(s->fname); + SAFE_FREE(s); + } + } +} diff --git a/source3/nmbd/nmbd_winsproxy.c b/source3/nmbd/nmbd_winsproxy.c new file mode 100644 index 0000000..163d2c0 --- /dev/null +++ b/source3/nmbd/nmbd_winsproxy.c @@ -0,0 +1,233 @@ +/* + 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 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "nmbd/nmbd.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) +{ + unstring name; + struct packet_struct *original_packet; + struct subnet_record *orig_broadcast_subnet; + struct name_record *namerec = NULL; + uint16_t 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 *) ); + + if (rrec) { + nb_flags = get_nb_flags( rrec->rdata ); + num_ips = rrec->rdlength / 6; + } else { + nb_flags = 0; + num_ips = 0; + } + + 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 = SMB_MALLOC_ARRAY( struct in_addr, num_ips )) == 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->ttl == PERMANENT_TTL) { + ttl = lp_max_ttl(); + } + + pull_ascii_nstring(name, sizeof(name), nmbname->name); + add_name_to_subnet( orig_broadcast_subnet, name, + nmbname->name_type, nb_flags, ttl, + WINS_PROXY_NAME, num_ips, iplist ); + + if(iplist != &ip) { + SAFE_FREE(iplist); + } + + namerec = find_name_on_subnet(orig_broadcast_subnet, nmbname, FIND_ANY_NAME); + if (!namerec) { + DEBUG(0,("wins_proxy_name_query_request_success: failed to add " + "name %s to subnet %s !\n", + name, + orig_broadcast_subnet->subnet_name )); + return; + } + + /* + * 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_v4( 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 requester. \ +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 *)SMB_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) +{ + union { + struct userdata_struct ud; + char c[sizeof(struct userdata_struct) + sizeof(struct subrec *) + + sizeof(struct packet_struct *)+sizeof(long*)]; + } ud; + struct userdata_struct *userdata = &ud.ud; + unstring qname; + + 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. */ + pull_ascii_nstring(qname, sizeof(qname), question_name->name); + query_name( unicast_subnet, qname, question_name->name_type, + wins_proxy_name_query_request_success, + wins_proxy_name_query_request_fail, + userdata); +} diff --git a/source3/nmbd/nmbd_winsserver.c b/source3/nmbd/nmbd_winsserver.c new file mode 100644 index 0000000..ecae447 --- /dev/null +++ b/source3/nmbd/nmbd_winsserver.c @@ -0,0 +1,2706 @@ +/* + Unix SMB/CIFS implementation. + NBT netbios routines and daemon - version 2 + + Copyright (C) Jeremy Allison 1994-2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + + Converted to store WINS data in a tdb. Dec 2005. JRA. +*/ + +#include "includes.h" +#include "system/filesys.h" +#include "nmbd/nmbd.h" +#include "util_tdb.h" + +#define WINS_LIST "wins.dat" +#define WINS_VERSION 1 +#define WINSDB_VERSION 1 + +/**************************************************************************** + We don't store the NetBIOS scope in the wins.tdb. We key off the (utf8) netbios + name (65 bytes with the last byte being the name type). +*****************************************************************************/ + +TDB_CONTEXT *wins_tdb; + +/**************************************************************************** + Delete all the temporary name records on the in-memory linked list. +*****************************************************************************/ + +static void wins_delete_all_tmp_in_memory_records(void) +{ + struct name_record *nr = NULL; + struct name_record *nrnext = NULL; + + /* Delete all temporary name records on the wins subnet linked list. */ + for( nr = wins_server_subnet->namelist; nr; nr = nrnext) { + nrnext = nr->next; + DLIST_REMOVE(wins_server_subnet->namelist, nr); + SAFE_FREE(nr->data.ip); + SAFE_FREE(nr); + } +} + +/**************************************************************************** + Delete all the temporary 1b name records on the in-memory linked list. +*****************************************************************************/ + +static void wins_delete_all_1b_in_memory_records(void) +{ + struct name_record *nr = NULL; + struct name_record *nrnext = NULL; + + /* Delete all temporary 1b name records on the wins subnet linked list. */ + for( nr = wins_server_subnet->namelist; nr; nr = nrnext) { + nrnext = nr->next; + if (nr->name.name_type == 0x1b) { + DLIST_REMOVE(wins_server_subnet->namelist, nr); + SAFE_FREE(nr->data.ip); + SAFE_FREE(nr); + } + } +} + +/**************************************************************************** + Convert a wins.tdb record to a struct name_record. Add in our lp_netbios_scope(). +*****************************************************************************/ + +static struct name_record *wins_record_to_name_record(TDB_DATA key, TDB_DATA data) +{ + struct name_record *namerec = NULL; + uint16_t nb_flags; + unsigned char nr_src; + uint32_t death_time, refresh_time; + uint32_t id_low, id_high; + uint32_t saddr; + uint32_t wins_flags; + uint32_t num_ips; + size_t len; + int i; + + if (data.dptr == NULL || data.dsize == 0) { + return NULL; + } + + /* Min size is "wbddddddd" + 1 ip address (4). */ + if (data.dsize < 2 + 1 + (7*4) + 4) { + return NULL; + } + + len = tdb_unpack(data.dptr, data.dsize, + "wbddddddd", + &nb_flags, + &nr_src, + &death_time, + &refresh_time, + &id_low, + &id_high, + &saddr, + &wins_flags, + &num_ips ); + + namerec = SMB_MALLOC_P(struct name_record); + if (!namerec) { + return NULL; + } + ZERO_STRUCTP(namerec); + + namerec->data.ip = SMB_MALLOC_ARRAY(struct in_addr, num_ips); + if (!namerec->data.ip) { + SAFE_FREE(namerec); + return NULL; + } + + namerec->subnet = wins_server_subnet; + push_ascii_nstring(namerec->name.name, (const char *)key.dptr); + namerec->name.name_type = key.dptr[sizeof(unstring)]; + /* Add the scope. */ + push_ascii(namerec->name.scope, lp_netbios_scope(), 64, STR_TERMINATE); + + /* We're using a byte-by-byte compare, so we must be sure that + * unused space doesn't have garbage in it. + */ + + for( i = strlen( namerec->name.name ); i < sizeof( namerec->name.name ); i++ ) { + namerec->name.name[i] = '\0'; + } + for( i = strlen( namerec->name.scope ); i < sizeof( namerec->name.scope ); i++ ) { + namerec->name.scope[i] = '\0'; + } + + namerec->data.nb_flags = nb_flags; + namerec->data.source = (enum name_source)nr_src; + namerec->data.death_time = (time_t)death_time; + namerec->data.refresh_time = (time_t)refresh_time; + namerec->data.id = id_low; + namerec->data.id |= ((uint64_t)id_high << 32); + namerec->data.wins_ip.s_addr = saddr; + namerec->data.wins_flags = wins_flags, + namerec->data.num_ips = num_ips; + + for (i = 0; i < num_ips; i++) { + namerec->data.ip[i].s_addr = IVAL(data.dptr, len + (i*4)); + } + + return namerec; +} + +/**************************************************************************** + Convert a struct name_record to a wins.tdb record. Ignore the scope. +*****************************************************************************/ + +static TDB_DATA name_record_to_wins_record(const struct name_record *namerec) +{ + TDB_DATA data; + size_t len = 0; + int i; + uint32_t id_low = (namerec->data.id & 0xFFFFFFFF); + uint32_t id_high = (namerec->data.id >> 32) & 0xFFFFFFFF; + + ZERO_STRUCT(data); + + len = (2 + 1 + (7*4)); /* "wbddddddd" */ + len += (namerec->data.num_ips * 4); + + data.dptr = (uint8_t *)SMB_MALLOC(len); + if (!data.dptr) { + return data; + } + data.dsize = len; + + len = tdb_pack(data.dptr, data.dsize, "wbddddddd", + namerec->data.nb_flags, + (unsigned char)namerec->data.source, + (uint32_t)namerec->data.death_time, + (uint32_t)namerec->data.refresh_time, + id_low, + id_high, + (uint32_t)namerec->data.wins_ip.s_addr, + (uint32_t)namerec->data.wins_flags, + (uint32_t)namerec->data.num_ips ); + + for (i = 0; i < namerec->data.num_ips; i++) { + SIVAL(data.dptr, len + (i*4), namerec->data.ip[i].s_addr); + } + + return data; +} + +/**************************************************************************** + Create key. Key is UNIX codepage namestring (usually utf8 64 byte len) with 1 byte type. +*****************************************************************************/ + +static TDB_DATA name_to_key(const struct nmb_name *nmbname) +{ + static char keydata[sizeof(unstring) + 1]; + TDB_DATA key; + + memset(keydata, '\0', sizeof(keydata)); + + pull_ascii_nstring(keydata, sizeof(unstring), nmbname->name); + (void)strupper_m(keydata); + keydata[sizeof(unstring)] = nmbname->name_type; + key.dptr = (uint8_t *)keydata; + key.dsize = sizeof(keydata); + + return key; +} + +/**************************************************************************** + Lookup a given name in the wins.tdb and create a temporary malloc'ed data struct + on the linked list. We will free this later in XXXX(). +*****************************************************************************/ + +struct name_record *find_name_on_wins_subnet(const struct nmb_name *nmbname, bool self_only) +{ + TDB_DATA data, key; + struct name_record *nr = NULL; + struct name_record *namerec = NULL; + + if (!wins_tdb) { + return NULL; + } + + key = name_to_key(nmbname); + data = tdb_fetch(wins_tdb, key); + + if (data.dsize == 0) { + return NULL; + } + + namerec = wins_record_to_name_record(key, data); + + /* done with the this */ + + SAFE_FREE( data.dptr ); + + if (!namerec) { + return NULL; + } + + /* Self names only - these include permanent names. */ + if( self_only && (namerec->data.source != SELF_NAME) && (namerec->data.source != PERMANENT_NAME) ) { + DEBUG( 9, ( "find_name_on_wins_subnet: self name %s NOT FOUND\n", nmb_namestr(nmbname) ) ); + SAFE_FREE(namerec->data.ip); + SAFE_FREE(namerec); + return NULL; + } + + /* Search for this name record on the list. Replace it if found. */ + + for( nr = wins_server_subnet->namelist; nr; nr = nr->next) { + if (memcmp(nmbname->name, nr->name.name, 16) == 0) { + /* Delete it. */ + DLIST_REMOVE(wins_server_subnet->namelist, nr); + SAFE_FREE(nr->data.ip); + SAFE_FREE(nr); + break; + } + } + + DLIST_ADD(wins_server_subnet->namelist, namerec); + return namerec; +} + +/**************************************************************************** + Overwrite or add a given name in the wins.tdb. +*****************************************************************************/ + +static bool store_or_replace_wins_namerec(const struct name_record *namerec, int tdb_flag) +{ + TDB_DATA key, data; + int ret; + + if (!wins_tdb) { + return False; + } + + key = name_to_key(&namerec->name); + data = name_record_to_wins_record(namerec); + + if (data.dptr == NULL) { + return False; + } + + ret = tdb_store(wins_tdb, key, data, tdb_flag); + + SAFE_FREE(data.dptr); + return (ret == 0) ? True : False; +} + +/**************************************************************************** + Overwrite a given name in the wins.tdb. +*****************************************************************************/ + +bool wins_store_changed_namerec(const struct name_record *namerec) +{ + return store_or_replace_wins_namerec(namerec, TDB_REPLACE); +} + +/**************************************************************************** + Primary interface into creating and overwriting records in the wins.tdb. +*****************************************************************************/ + +bool add_name_to_wins_subnet(const struct name_record *namerec) +{ + return store_or_replace_wins_namerec(namerec, TDB_INSERT); +} + +/**************************************************************************** + Delete a given name in the tdb and remove the temporary malloc'ed data struct + on the linked list. +*****************************************************************************/ + +bool remove_name_from_wins_namelist(struct name_record *namerec) +{ + TDB_DATA key; + int ret; + + if (!wins_tdb) { + return False; + } + + key = name_to_key(&namerec->name); + ret = tdb_delete(wins_tdb, key); + + DLIST_REMOVE(wins_server_subnet->namelist, namerec); + + /* namerec must be freed by the caller */ + + return (ret == 0) ? True : False; +} + +/**************************************************************************** + Dump out the complete namelist. +*****************************************************************************/ + +static int traverse_fn(TDB_CONTEXT *tdb, TDB_DATA kbuf, TDB_DATA dbuf, void *state) +{ + struct name_record *namerec = NULL; + FILE *fp = (FILE *)state; + + if (kbuf.dsize != sizeof(unstring) + 1) { + return 0; + } + + namerec = wins_record_to_name_record(kbuf, dbuf); + if (!namerec) { + return 0; + } + + dump_name_record(namerec, fp); + + SAFE_FREE(namerec->data.ip); + SAFE_FREE(namerec); + return 0; +} + +void dump_wins_subnet_namelist(FILE *fp) +{ + tdb_traverse(wins_tdb, traverse_fn, (void *)fp); +} + +/**************************************************************************** + Change the wins owner address in the record. +*****************************************************************************/ + +static void update_wins_owner(struct name_record *namerec, struct in_addr wins_ip) +{ + namerec->data.wins_ip=wins_ip; +} + +/**************************************************************************** + Create the wins flags based on the nb flags and the input value. +*****************************************************************************/ + +static void update_wins_flag(struct name_record *namerec, int flags) +{ + namerec->data.wins_flags=0x0; + + /* if it's a group, it can be a normal or a special one */ + if (namerec->data.nb_flags & NB_GROUP) { + if (namerec->name.name_type==0x1C) { + namerec->data.wins_flags|=WINS_SGROUP; + } else { + if (namerec->data.num_ips>1) { + namerec->data.wins_flags|=WINS_SGROUP; + } else { + namerec->data.wins_flags|=WINS_NGROUP; + } + } + } else { + /* can be unique or multi-homed */ + if (namerec->data.num_ips>1) { + namerec->data.wins_flags|=WINS_MHOMED; + } else { + namerec->data.wins_flags|=WINS_UNIQUE; + } + } + + /* the node type are the same bits */ + namerec->data.wins_flags|=namerec->data.nb_flags&NB_NODETYPEMASK; + + /* the static bit is elsewhere */ + if (namerec->data.death_time == PERMANENT_TTL) { + namerec->data.wins_flags|=WINS_STATIC; + } + + /* and add the given bits */ + namerec->data.wins_flags|=flags; + + DEBUG(8,("update_wins_flag: nbflags: 0x%x, ttl: %d, flags: 0x%x, winsflags: 0x%x\n", + namerec->data.nb_flags, (int)namerec->data.death_time, flags, namerec->data.wins_flags)); +} + +/**************************************************************************** + Return the general ID value and increase it if requested. +*****************************************************************************/ + +static void get_global_id_and_update(uint64_t *current_id, bool update) +{ + /* + * it's kept as a static here, to prevent people from messing + * with the value directly + */ + + static uint64_t general_id = 1; + + DEBUG(5,("get_global_id_and_update: updating version ID: %d\n", (int)general_id)); + + *current_id = general_id; + + if (update) { + general_id++; + } +} + +/**************************************************************************** + Possibly call the WINS hook external program when a WINS change is made. + Also stores the changed record back in the wins_tdb. +*****************************************************************************/ + +static void wins_hook(const char *operation, struct name_record *namerec, int ttl) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + char *command = NULL; + char *cmd = lp_wins_hook(talloc_tos(), lp_sub); + char *p, *namestr; + int i; + TALLOC_CTX *ctx = talloc_tos(); + + wins_store_changed_namerec(namerec); + + if (!cmd || !*cmd) { + return; + } + + for (p=namerec->name.name; *p; p++) { + if (!(isalnum((int)*p) || strchr_m("._-",*p))) { + DEBUG(3,("not calling wins hook for invalid name %s\n", nmb_namestr(&namerec->name))); + return; + } + } + + /* Use the name without the nametype (and scope) appended */ + + namestr = nmb_namestr(&namerec->name); + if ((p = strchr(namestr, '<'))) { + *p = 0; + } + + command = talloc_asprintf(ctx, + "%s %s %s %02x %d", + cmd, + operation, + namestr, + namerec->name.name_type, + ttl); + if (!command) { + return; + } + + for (i=0;i<namerec->data.num_ips;i++) { + command = talloc_asprintf_append(command, + " %s", + inet_ntoa(namerec->data.ip[i])); + if (!command) { + return; + } + } + + DEBUG(3,("calling wins hook for %s\n", nmb_namestr(&namerec->name))); + smbrun(command, NULL, NULL); + TALLOC_FREE(command); +} + +/**************************************************************************** +Determine if this packet should be allocated to the WINS server. +*****************************************************************************/ + +bool packet_is_for_wins_server(struct packet_struct *packet) +{ + struct nmb_packet *nmb = &packet->packet.nmb; + + /* Only unicast packets go to a WINS server. */ + if((wins_server_subnet == NULL) || (nmb->header.nm_flags.bcast == True)) { + DEBUG(10, ("packet_is_for_wins_server: failing WINS test #1.\n")); + return False; + } + + /* Check for node status requests. */ + if (nmb->question.question_type != QUESTION_TYPE_NB_QUERY) { + return False; + } + + switch(nmb->header.opcode) { + /* + * A WINS server issues WACKS, not receives them. + */ + case NMB_WACK_OPCODE: + DEBUG(10, ("packet_is_for_wins_server: failing WINS test #2 (WACK).\n")); + return False; + /* + * A WINS server only processes registration and + * release requests, not responses. + */ + case NMB_NAME_REG_OPCODE: + case NMB_NAME_MULTIHOMED_REG_OPCODE: + case NMB_NAME_REFRESH_OPCODE_8: /* ambiguity in rfc1002 about which is correct. */ + case NMB_NAME_REFRESH_OPCODE_9: /* WinNT uses 8 by default. */ + if(nmb->header.response) { + DEBUG(10, ("packet_is_for_wins_server: failing WINS test #3 (response = 1).\n")); + return False; + } + break; + + case NMB_NAME_RELEASE_OPCODE: + if(nmb->header.response) { + DEBUG(10, ("packet_is_for_wins_server: failing WINS test #4 (response = 1).\n")); + return False; + } + break; + + /* + * Only process unicast name queries with rd = 1. + */ + case NMB_NAME_QUERY_OPCODE: + if(!nmb->header.response && !nmb->header.nm_flags.recursion_desired) { + DEBUG(10, ("packet_is_for_wins_server: failing WINS test #5 (response = 1).\n")); + return False; + } + break; + } + + return True; +} + +/**************************************************************************** +Utility function to decide what ttl to give a register/refresh request. +*****************************************************************************/ + +static int get_ttl_from_packet(struct nmb_packet *nmb) +{ + int ttl = nmb->additional->ttl; + + if (ttl < lp_min_wins_ttl()) { + ttl = lp_min_wins_ttl(); + } + + if (ttl > lp_max_wins_ttl()) { + ttl = lp_max_wins_ttl(); + } + + return ttl; +} + +/**************************************************************************** +Load or create the WINS database. +*****************************************************************************/ + +bool initialise_wins(void) +{ + time_t time_now = time(NULL); + FILE *fp; + char line[1024]; + char *db_path; + char *list_path; + + if(!lp_we_are_a_wins_server()) { + return True; + } + + db_path = state_path(talloc_tos(), "wins.tdb"); + if (db_path == NULL) { + return false; + } + + /* Open the wins.tdb. */ + wins_tdb = tdb_open_log(db_path, 0, TDB_DEFAULT|TDB_CLEAR_IF_FIRST|TDB_INCOMPATIBLE_HASH, + O_CREAT|O_RDWR, 0600); + TALLOC_FREE(db_path); + if (!wins_tdb) { + DEBUG(0,("initialise_wins: failed to open wins.tdb. Error was %s\n", + strerror(errno) )); + return False; + } + + tdb_store_int32(wins_tdb, "WINSDB_VERSION", WINSDB_VERSION); + + add_samba_names_to_subnet(wins_server_subnet); + + list_path = state_path(talloc_tos(), WINS_LIST); + if (list_path == NULL) { + tdb_close(wins_tdb); + return false; + } + + fp = fopen(list_path, "r"); + TALLOC_FREE(list_path); + if (fp == NULL) { + DEBUG(2,("initialise_wins: Can't open wins database file %s. Error was %s\n", + WINS_LIST, strerror(errno) )); + return True; + } + + while (!feof(fp)) { + char *name_str = NULL; + char *ip_str = NULL; + char *ttl_str = NULL, *nb_flags_str = NULL; + unsigned int num_ips; + char *name = NULL; + struct in_addr *ip_list = NULL; + int type = 0; + int nb_flags; + int ttl; + const char *ptr; + char *p = NULL; + bool got_token; + bool was_ip; + int i; + unsigned int hash; + int version; + TALLOC_CTX *frame = NULL; + + /* Read a line from the wins.dat file. Strips whitespace + from the beginning and end of the line. */ + if (!fgets_slash(NULL, line, sizeof(line), fp)) { + continue; + } + + if (*line == '#') { + continue; + } + + if (strncmp(line,"VERSION ", 8) == 0) { + if (sscanf(line,"VERSION %d %u", &version, &hash) != 2 || + version != WINS_VERSION) { + DEBUG(0,("Discarding invalid wins.dat file [%s]\n",line)); + fclose(fp); + return True; + } + continue; + } + + ptr = line; + + /* + * Now we handle multiple IP addresses per name we need + * to iterate over the line twice. The first time to + * determine how many IP addresses there are, the second + * time to actually parse them into the ip_list array. + */ + + frame = talloc_stackframe(); + if (!next_token_talloc(frame,&ptr,&name_str,NULL)) { + DEBUG(0,("initialise_wins: Failed to parse name when parsing line %s\n", line )); + TALLOC_FREE(frame); + continue; + } + + if (!next_token_talloc(frame,&ptr,&ttl_str,NULL)) { + DEBUG(0,("initialise_wins: Failed to parse time to live when parsing line %s\n", line )); + TALLOC_FREE(frame); + continue; + } + + /* + * Determine the number of IP addresses per line. + */ + num_ips = 0; + do { + got_token = next_token_talloc(frame,&ptr,&ip_str,NULL); + was_ip = False; + + if(got_token && strchr(ip_str, '.')) { + num_ips++; + was_ip = True; + } + } while(got_token && was_ip); + + if(num_ips == 0) { + DEBUG(0,("initialise_wins: Missing IP address when parsing line %s\n", line )); + TALLOC_FREE(frame); + continue; + } + + if(!got_token) { + DEBUG(0,("initialise_wins: Missing nb_flags when parsing line %s\n", line )); + TALLOC_FREE(frame); + continue; + } + + /* Allocate the space for the ip_list. */ + if((ip_list = SMB_MALLOC_ARRAY( struct in_addr, num_ips)) == NULL) { + DEBUG(0,("initialise_wins: Malloc fail !\n")); + fclose(fp); + TALLOC_FREE(frame); + return False; + } + + /* Reset and re-parse the line. */ + ptr = line; + next_token_talloc(frame,&ptr,&name_str,NULL); + next_token_talloc(frame,&ptr,&ttl_str,NULL); + for(i = 0; i < num_ips; i++) { + next_token_talloc(frame,&ptr, &ip_str, NULL); + ip_list[i] = interpret_addr2(ip_str); + } + next_token_talloc(frame,&ptr,&nb_flags_str,NULL); + + /* + * Deal with SELF or REGISTER name encoding. Default is REGISTER + * for compatibility with old nmbds. + */ + + if(nb_flags_str[strlen(nb_flags_str)-1] == 'S') { + DEBUG(5,("initialise_wins: Ignoring SELF name %s\n", line)); + SAFE_FREE(ip_list); + TALLOC_FREE(frame); + continue; + } + + if(nb_flags_str[strlen(nb_flags_str)-1] == 'R') { + nb_flags_str[strlen(nb_flags_str)-1] = '\0'; + } + + /* Netbios name. # divides the name from the type (hex): netbios#xx */ + name = name_str; + + if((p = strchr(name,'#')) != NULL) { + *p = 0; + sscanf(p+1,"%x",&type); + } + + /* Decode the netbios flags (hex) and the time-to-live (in seconds). */ + sscanf(nb_flags_str,"%x",&nb_flags); + sscanf(ttl_str,"%d",&ttl); + + /* add all entries that have 60 seconds or more to live */ + if ((ttl - 60) > time_now || ttl == PERMANENT_TTL) { + if(ttl != PERMANENT_TTL) { + ttl -= time_now; + } + + DEBUG( 4, ("initialise_wins: add name: %s#%02x ttl = %d first IP %s flags = %2x\n", + name, type, ttl, inet_ntoa(ip_list[0]), nb_flags)); + + (void)add_name_to_subnet( wins_server_subnet, name, type, nb_flags, + ttl, REGISTER_NAME, num_ips, ip_list ); + } else { + DEBUG(4, ("initialise_wins: not adding name (ttl problem) " + "%s#%02x ttl = %d first IP %s flags = %2x\n", + name, type, ttl, inet_ntoa(ip_list[0]), nb_flags)); + } + + TALLOC_FREE(frame); + SAFE_FREE(ip_list); + } + + fclose(fp); + return True; +} + +/**************************************************************************** +Send a WINS WACK (Wait ACKnowledgement) response. +**************************************************************************/ + +static void send_wins_wack_response(int ttl, struct packet_struct *p) +{ + struct nmb_packet *nmb = &p->packet.nmb; + unsigned char rdata[2]; + + rdata[0] = rdata[1] = 0; + + /* Taken from nmblib.c - we need to send back almost + identical bytes from the requesting packet header. */ + + rdata[0] = (nmb->header.opcode & 0xF) << 3; + if (nmb->header.nm_flags.authoritative && nmb->header.response) { + rdata[0] |= 0x4; + } + if (nmb->header.nm_flags.trunc) { + rdata[0] |= 0x2; + } + if (nmb->header.nm_flags.recursion_desired) { + rdata[0] |= 0x1; + } + if (nmb->header.nm_flags.recursion_available && nmb->header.response) { + rdata[1] |= 0x80; + } + if (nmb->header.nm_flags.bcast) { + rdata[1] |= 0x10; + } + + reply_netbios_packet(p, /* Packet to reply to. */ + 0, /* Result code. */ + NMB_WAIT_ACK, /* nmbd type code. */ + NMB_WACK_OPCODE, /* opcode. */ + ttl, /* ttl. */ + (char *)rdata, /* data to send. */ + 2); /* data length. */ +} + +/**************************************************************************** +Send a WINS name registration response. +**************************************************************************/ + +static void send_wins_name_registration_response(int rcode, int ttl, struct packet_struct *p) +{ + struct nmb_packet *nmb = &p->packet.nmb; + char rdata[6]; + + memcpy(&rdata[0], &nmb->additional->rdata[0], 6); + + reply_netbios_packet(p, /* Packet to reply to. */ + rcode, /* Result code. */ + WINS_REG, /* nmbd type code. */ + NMB_NAME_REG_OPCODE, /* opcode. */ + ttl, /* ttl. */ + rdata, /* data to send. */ + 6); /* data length. */ +} + +/*********************************************************************** + Deal with a name refresh request to a WINS server. +************************************************************************/ + +void wins_process_name_refresh_request( struct subnet_record *subrec, + struct packet_struct *p ) +{ + struct nmb_packet *nmb = &p->packet.nmb; + struct nmb_name *question = &nmb->question.question_name; + bool bcast = nmb->header.nm_flags.bcast; + uint16_t nb_flags = get_nb_flags(nmb->additional->rdata); + bool group = (nb_flags & NB_GROUP) ? True : False; + struct name_record *namerec = NULL; + int ttl = get_ttl_from_packet(nmb); + struct in_addr from_ip; + struct in_addr our_fake_ip; + + our_fake_ip = interpret_addr2("0.0.0.0"); + putip( (char *)&from_ip, &nmb->additional->rdata[2] ); + + if(bcast) { + /* + * We should only get unicast name refresh packets here. + * Anyone trying to refresh broadcast should not be going + * to a WINS server. Log an error here. + */ + if( DEBUGLVL( 0 ) ) { + dbgtext( "wins_process_name_refresh_request: " ); + dbgtext( "Broadcast name refresh request received " ); + dbgtext( "for name %s ", nmb_namestr(question) ); + dbgtext( "from IP %s ", inet_ntoa(from_ip) ); + dbgtext( "on subnet %s. ", subrec->subnet_name ); + dbgtext( "Error - Broadcasts should not be sent " ); + dbgtext( "to a WINS server\n" ); + } + return; + } + + if( DEBUGLVL( 3 ) ) { + dbgtext( "wins_process_name_refresh_request: " ); + dbgtext( "Name refresh for name %s IP %s\n", + nmb_namestr(question), inet_ntoa(from_ip) ); + } + + /* + * See if the name already exists. + * If not, handle it as a name registration and return. + */ + namerec = find_name_on_subnet(subrec, question, FIND_ANY_NAME); + + /* + * If this is a refresh request and the name doesn't exist then + * treat it like a registration request. This allows us to recover + * from errors (tridge) + */ + if(namerec == NULL) { + if( DEBUGLVL( 3 ) ) { + dbgtext( "wins_process_name_refresh_request: " ); + dbgtext( "Name refresh for name %s ", + nmb_namestr( question ) ); + dbgtext( "and the name does not exist. Treating " ); + dbgtext( "as registration.\n" ); + } + wins_process_name_registration_request(subrec,p); + return; + } + + /* + * if the name is present but not active, simply remove it + * and treat the refresh request as a registration & return. + */ + if (namerec != NULL && !WINS_STATE_ACTIVE(namerec)) { + if( DEBUGLVL( 5 ) ) { + dbgtext( "wins_process_name_refresh_request: " ); + dbgtext( "Name (%s) in WINS ", nmb_namestr(question) ); + dbgtext( "was not active - removing it.\n" ); + } + remove_name_from_namelist( subrec, namerec ); + namerec = NULL; + wins_process_name_registration_request( subrec, p ); + return; + } + + /* + * Check that the group bits for the refreshing name and the + * name in our database match. If not, refuse the refresh. + * [crh: Why RFS_ERR instead of ACT_ERR? Is this what MS does?] + */ + if( (namerec != NULL) && + ( (group && !NAME_GROUP(namerec)) + || (!group && NAME_GROUP(namerec)) ) ) { + if( DEBUGLVL( 3 ) ) { + dbgtext( "wins_process_name_refresh_request: " ); + dbgtext( "Name %s ", nmb_namestr(question) ); + dbgtext( "group bit = %s does not match ", + group ? "True" : "False" ); + dbgtext( "group bit in WINS for this name.\n" ); + } + send_wins_name_registration_response(RFS_ERR, 0, p); + return; + } + + /* + * For a unique name check that the person refreshing the name is + * one of the registered IP addresses. If not - fail the refresh. + * Do the same for group names with a type of 0x1c. + * Just return success for unique 0x1d refreshes. For normal group + * names update the ttl and return success. + */ + if( (!group || (group && (question->name_type == 0x1c))) + && find_ip_in_name_record(namerec, from_ip) ) { + /* + * Update the ttl. + */ + update_name_ttl(namerec, ttl); + + /* + * if the record is a replica: + * we take ownership and update the version ID. + */ + if (!ip_equal_v4(namerec->data.wins_ip, our_fake_ip)) { + update_wins_owner(namerec, our_fake_ip); + get_global_id_and_update(&namerec->data.id, True); + } + + send_wins_name_registration_response(0, ttl, p); + wins_hook("refresh", namerec, ttl); + return; + } else if((group && (question->name_type == 0x1c))) { + /* + * Added by crh for bug #1079. + * Fix from Bert Driehuis + */ + if( DEBUGLVL( 3 ) ) { + dbgtext( "wins_process_name_refresh_request: " ); + dbgtext( "Name refresh for name %s, ", + nmb_namestr(question) ); + dbgtext( "but IP address %s ", inet_ntoa(from_ip) ); + dbgtext( "is not yet associated with " ); + dbgtext( "that name. Treating as registration.\n" ); + } + wins_process_name_registration_request(subrec,p); + return; + } else if(group) { + /* + * Normal groups are all registered with an IP address of + * 255.255.255.255 so we can't search for the IP address. + */ + update_name_ttl(namerec, ttl); + wins_hook("refresh", namerec, ttl); + send_wins_name_registration_response(0, ttl, p); + return; + } else if(!group && (question->name_type == 0x1d)) { + /* + * Special name type - just pretend the refresh succeeded. + */ + send_wins_name_registration_response(0, ttl, p); + return; + } else { + /* + * Fail the refresh. + */ + if( DEBUGLVL( 3 ) ) { + dbgtext( "wins_process_name_refresh_request: " ); + dbgtext( "Name refresh for name %s with IP %s ", + nmb_namestr(question), inet_ntoa(from_ip) ); + dbgtext( "and is IP is not known to the name.\n" ); + } + send_wins_name_registration_response(RFS_ERR, 0, p); + return; + } +} + +/*********************************************************************** + Deal with a name registration request query success to a client that + owned the name. + + We have a locked pointer to the original packet stashed away in the + userdata pointer. The success here is actually a failure as it means + the client we queried wants to keep the name, so we must return + a registration failure to the original requester. +************************************************************************/ + +static void wins_register_query_success(struct subnet_record *subrec, + struct userdata_struct *userdata, + struct nmb_name *question_name, + struct in_addr ip, + struct res_rec *answers) +{ + struct packet_struct *orig_reg_packet; + + memcpy((char *)&orig_reg_packet, userdata->data, sizeof(struct packet_struct *)); + + DEBUG(3,("wins_register_query_success: Original client at IP %s still wants the \ +name %s. Rejecting registration request.\n", inet_ntoa(ip), nmb_namestr(question_name) )); + + send_wins_name_registration_response(ACT_ERR, 0, orig_reg_packet); + + orig_reg_packet->locked = False; + free_packet(orig_reg_packet); +} + +/*********************************************************************** + Deal with a name registration request query failure to a client that + owned the name. + + We have a locked pointer to the original packet stashed away in the + userdata pointer. The failure here is actually a success as it means + the client we queried didn't want to keep the name, so we can remove + the old name record and then successfully add the new name. +************************************************************************/ + +static void wins_register_query_fail(struct subnet_record *subrec, + struct response_record *rrec, + struct nmb_name *question_name, + int rcode) +{ + struct userdata_struct *userdata = rrec->userdata; + struct packet_struct *orig_reg_packet; + struct name_record *namerec = NULL; + + memcpy((char *)&orig_reg_packet, userdata->data, sizeof(struct packet_struct *)); + + /* + * We want to just add the name, as we now know the original owner + * didn't want it. But we can't just do that as an arbitrary + * amount of time may have taken place between the name query + * request and this timeout/error response. So we check that + * the name still exists and is in the same state - if so + * we remove it and call wins_process_name_registration_request() + * as we know it will do the right thing now. + */ + + namerec = find_name_on_subnet(subrec, question_name, FIND_ANY_NAME); + + if ((namerec != NULL) && (namerec->data.source == REGISTER_NAME) && + ip_equal_v4(rrec->packet->ip, *namerec->data.ip)) { + remove_name_from_namelist( subrec, namerec); + namerec = NULL; + } + + if(namerec == NULL) { + wins_process_name_registration_request(subrec, orig_reg_packet); + } else { + DEBUG(2,("wins_register_query_fail: The state of the WINS database changed between " + "querying for name %s in order to replace it and this reply.\n", + nmb_namestr(question_name) )); + } + + orig_reg_packet->locked = False; + free_packet(orig_reg_packet); +} + +/*********************************************************************** + Deal with a name registration request to a WINS server. + + Use the following pseudocode : + + registering_group + | + | + +--------name exists + | | + | | + | +--- existing name is group + | | | + | | | + | | +--- add name (return). + | | + | | + | +--- exiting name is unique + | | + | | + | +--- query existing owner (return). + | + | + +--------name doesn't exist + | + | + +--- add name (return). + + registering_unique + | + | + +--------name exists + | | + | | + | +--- existing name is group + | | | + | | | + | | +--- fail add (return). + | | + | | + | +--- exiting name is unique + | | + | | + | +--- query existing owner (return). + | + | + +--------name doesn't exist + | + | + +--- add name (return). + + As can be seen from the above, the two cases may be collapsed onto each + other with the exception of the case where the name already exists and + is a group name. This case we handle with an if statement. + +************************************************************************/ + +void wins_process_name_registration_request(struct subnet_record *subrec, + struct packet_struct *p) +{ + unstring name; + struct nmb_packet *nmb = &p->packet.nmb; + struct nmb_name *question = &nmb->question.question_name; + bool bcast = nmb->header.nm_flags.bcast; + uint16_t nb_flags = get_nb_flags(nmb->additional->rdata); + int ttl = get_ttl_from_packet(nmb); + struct name_record *namerec = NULL; + struct in_addr from_ip; + bool registering_group_name = (nb_flags & NB_GROUP) ? True : False; + struct in_addr our_fake_ip; + + our_fake_ip = interpret_addr2("0.0.0.0"); + putip((char *)&from_ip,&nmb->additional->rdata[2]); + + if(bcast) { + /* + * We should only get unicast name registration packets here. + * Anyone trying to register broadcast should not be going to a WINS + * server. Log an error here. + */ + + DEBUG(0,("wins_process_name_registration_request: broadcast name registration request \ +received for name %s from IP %s on subnet %s. Error - should not be sent to WINS server\n", + nmb_namestr(question), inet_ntoa(from_ip), subrec->subnet_name)); + return; + } + + DEBUG(3,("wins_process_name_registration_request: %s name registration for name %s \ +IP %s\n", registering_group_name ? "Group" : "Unique", nmb_namestr(question), inet_ntoa(from_ip) )); + + /* + * See if the name already exists. + */ + + namerec = find_name_on_subnet(subrec, question, FIND_ANY_NAME); + + /* + * if the record exists but NOT in active state, + * consider it dead. + */ + if ( (namerec != NULL) && !WINS_STATE_ACTIVE(namerec)) { + DEBUG(5,("wins_process_name_registration_request: Name (%s) in WINS was \ +not active - removing it.\n", nmb_namestr(question) )); + remove_name_from_namelist( subrec, namerec ); + namerec = NULL; + } + + /* + * Deal with the case where the name found was a dns entry. + * Remove it as we now have a NetBIOS client registering the + * name. + */ + + if( (namerec != NULL) && ( (namerec->data.source == DNS_NAME) || (namerec->data.source == DNSFAIL_NAME) ) ) { + DEBUG(5,("wins_process_name_registration_request: Name (%s) in WINS was \ +a dns lookup - removing it.\n", nmb_namestr(question) )); + remove_name_from_namelist( subrec, namerec ); + namerec = NULL; + } + + /* + * Reject if the name exists and is not a REGISTER_NAME. + * (ie. Don't allow any static names to be overwritten. + */ + + if((namerec != NULL) && (namerec->data.source != REGISTER_NAME)) { + DEBUG( 3, ( "wins_process_name_registration_request: Attempt \ +to register name %s. Name already exists in WINS with source type %d.\n", + nmb_namestr(question), namerec->data.source )); + send_wins_name_registration_response(RFS_ERR, 0, p); + return; + } + + /* + * Special policy decisions based on MS documentation. + * 1). All group names (except names ending in 0x1c) are added as 255.255.255.255. + * 2). All unique names ending in 0x1d are ignored, although a positive response is sent. + */ + + /* + * A group name is always added as the local broadcast address, except + * for group names ending in 0x1c. + * Group names with type 0x1c are registered with individual IP addresses. + */ + + if(registering_group_name && (question->name_type != 0x1c)) { + from_ip = interpret_addr2("255.255.255.255"); + } + + /* + * Ignore all attempts to register a unique 0x1d name, although return success. + */ + + if(!registering_group_name && (question->name_type == 0x1d)) { + DEBUG(3,("wins_process_name_registration_request: Ignoring request \ +to register name %s from IP %s.\n", nmb_namestr(question), inet_ntoa(p->ip) )); + send_wins_name_registration_response(0, ttl, p); + return; + } + + /* + * Next two cases are the 'if statement' mentioned above. + */ + + if((namerec != NULL) && NAME_GROUP(namerec)) { + if(registering_group_name) { + /* + * If we are adding a group name, the name exists and is also a group entry just add this + * IP address to it and update the ttl. + */ + + DEBUG(3,("wins_process_name_registration_request: Adding IP %s to group name %s.\n", + inet_ntoa(from_ip), nmb_namestr(question) )); + + /* + * Check the ip address is not already in the group. + */ + + if(!find_ip_in_name_record(namerec, from_ip)) { + /* + * Need to emulate the behaviour of Windows, as + * described in: + * http://lists.samba.org/archive/samba-technical/2001-October/016236.html + * (is there an MS reference for this + * somewhere?) because if the 1c list gets over + * 86 entries, the reply packet is too big + * (rdata>576 bytes) so no reply is sent. + * + * Keep only the "latest" 25 records, while + * ensuring that the PDC (0x1b) is never removed + * We do this by removing the first entry that + * isn't the 1b entry for the same name, + * on the grounds that insertion is at the end + * of the list, so the oldest entries are at + * the start. + * + */ + while(namerec->data.num_ips>=25) { + struct name_record *name1brec = NULL; + + /* We only do this for 1c types. */ + if (namerec->name.name_type != 0x1c) { + break; + } + DEBUG(3,("wins_process_name_registration_request: " + "More than 25 IPs already in " + "the list. Looking for a 1b " + "record\n")); + + /* Ensure we have all the active 1b + * names on the list. */ + wins_delete_all_1b_in_memory_records(); + fetch_all_active_wins_1b_names(); + + /* Per the above, find the 1b record, + and then remove the first IP that isn't the same */ + for(name1brec = subrec->namelist; + name1brec; + name1brec = name1brec->next ) { + if( WINS_STATE_ACTIVE(name1brec) && + name1brec->name.name_type == 0x1b) { + DEBUG(3,("wins_process_name_registration_request: " + "Found the #1b record " + "with ip %s\n", + inet_ntoa(name1brec->data.ip[0]))); + break; + } + } + if(!name1brec) { + DEBUG(3,("wins_process_name_registration_request: " + "Didn't find a #1b name record. " + "Removing the first available " + "entry %s\n", + inet_ntoa(namerec->data.ip[0]))); + remove_ip_from_name_record(namerec, namerec->data.ip[0]); + wins_hook("delete", namerec, 0); + } else { + int i; + for(i=0; i<namerec->data.num_ips; i++) { + /* The name1brec should only have + * the single IP address in it, + * so we only check against the first one*/ + if(!ip_equal_v4( namerec->data.ip[i], name1brec->data.ip[0])) { + /* The i'th entry isn't the 1b address; delete it */ + DEBUG(3,("wins_process_name_registration_request: " + "Entry at %d is not the #1b address. " + "About to remove it\n", + i)); + remove_ip_from_name_record(namerec, namerec->data.ip[i]); + wins_hook("delete", namerec, 0); + break; + } + } + } + } + /* The list is guaranteed to be < 25 entries now + * - safe to add a new one */ + add_ip_to_name_record(namerec, from_ip); + /* we need to update the record for replication */ + get_global_id_and_update(&namerec->data.id, True); + + /* + * if the record is a replica, we must change + * the wins owner to us to make the replication updates + * it on the other wins servers. + * And when the partner will receive this record, + * it will update its own record. + */ + + update_wins_owner(namerec, our_fake_ip); + } + update_name_ttl(namerec, ttl); + wins_hook("refresh", namerec, ttl); + send_wins_name_registration_response(0, ttl, p); + return; + } else { + + /* + * If we are adding a unique name, the name exists in the WINS db + * and is a group name then reject the registration. + * + * explanation: groups have a higher priority than unique names. + */ + + DEBUG(3,("wins_process_name_registration_request: Attempt to register name %s. Name \ +already exists in WINS as a GROUP name.\n", nmb_namestr(question) )); + send_wins_name_registration_response(RFS_ERR, 0, p); + return; + } + } + + /* + * From here on down we know that if the name exists in the WINS db it is + * a unique name, not a group name. + */ + + /* + * If the name exists and is one of our names then check the + * registering IP address. If it's not one of ours then automatically + * reject without doing the query - we know we will reject it. + */ + + if ( namerec != NULL ) { + pull_ascii_nstring(name, sizeof(name), namerec->name.name); + if( is_myname(name) ) { + if(!ismyip_v4(from_ip)) { + DEBUG(3,("wins_process_name_registration_request: Attempt to register name %s. Name \ +is one of our (WINS server) names. Denying registration.\n", nmb_namestr(question) )); + send_wins_name_registration_response(RFS_ERR, 0, p); + return; + } else { + /* + * It's one of our names and one of our IP's - update the ttl. + */ + update_name_ttl(namerec, ttl); + wins_hook("refresh", namerec, ttl); + send_wins_name_registration_response(0, ttl, p); + return; + } + } + } else { + name[0] = '\0'; + } + + /* + * If the name exists and it is a unique registration and the registering IP + * is the same as the (single) already registered IP then just update the ttl. + * + * But not if the record is an active replica. IF it's a replica, it means it can be + * the same client which has moved and not yet expired. So we don't update + * the ttl in this case and go beyond to do a WACK and query the old client + */ + + if( !registering_group_name + && (namerec != NULL) + && (namerec->data.num_ips == 1) + && ip_equal_v4( namerec->data.ip[0], from_ip ) + && ip_equal_v4(namerec->data.wins_ip, our_fake_ip) ) { + update_name_ttl( namerec, ttl ); + wins_hook("refresh", namerec, ttl); + send_wins_name_registration_response( 0, ttl, p ); + return; + } + + /* + * Finally if the name exists do a query to the registering machine + * to see if they still claim to have the name. + */ + + if( namerec != NULL ) { + long *ud[(sizeof(struct userdata_struct) + sizeof(struct packet_struct *))/sizeof(long *) + 1]; + struct userdata_struct *userdata = (struct userdata_struct *)ud; + + /* + * First send a WACK to the registering machine. + */ + + send_wins_wack_response(60, p); + + /* + * When the reply comes back we need the original packet. + * Lock this so it won't be freed and then put it into + * the userdata structure. + */ + + p->locked = True; + + userdata = (struct userdata_struct *)ud; + + userdata->copy_fn = NULL; + userdata->free_fn = NULL; + userdata->userdata_len = sizeof(struct packet_struct *); + memcpy(userdata->data, (char *)&p, sizeof(struct packet_struct *) ); + + /* + * Use the new call to send a query directly to an IP address. + * This sends the query directly to the IP address, and ensures + * the recursion desired flag is not set (you were right Luke :-). + * This function should *only* be called from the WINS server + * code. JRA. + */ + + pull_ascii_nstring(name, sizeof(name), question->name); + query_name_from_wins_server( *namerec->data.ip, + name, + question->name_type, + wins_register_query_success, + wins_register_query_fail, + userdata ); + return; + } + + /* + * Name did not exist - add it. + */ + + pull_ascii_nstring(name, sizeof(name), question->name); + add_name_to_subnet( subrec, name, question->name_type, + nb_flags, ttl, REGISTER_NAME, 1, &from_ip); + + if ((namerec = find_name_on_subnet(subrec, question, FIND_ANY_NAME))) { + get_global_id_and_update(&namerec->data.id, True); + update_wins_owner(namerec, our_fake_ip); + update_wins_flag(namerec, WINS_ACTIVE); + wins_hook("add", namerec, ttl); + } + + send_wins_name_registration_response(0, ttl, p); +} + +/*********************************************************************** + Deal with a mutihomed name query success to the machine that + requested the multihomed name registration. + + We have a locked pointer to the original packet stashed away in the + userdata pointer. +************************************************************************/ + +static void wins_multihomed_register_query_success(struct subnet_record *subrec, + struct userdata_struct *userdata, + struct nmb_name *question_name, + struct in_addr ip, + struct res_rec *answers) +{ + struct packet_struct *orig_reg_packet; + struct nmb_packet *nmb; + struct name_record *namerec = NULL; + struct in_addr from_ip; + int ttl; + struct in_addr our_fake_ip; + + our_fake_ip = interpret_addr2("0.0.0.0"); + memcpy((char *)&orig_reg_packet, userdata->data, sizeof(struct packet_struct *)); + + nmb = &orig_reg_packet->packet.nmb; + + putip((char *)&from_ip,&nmb->additional->rdata[2]); + ttl = get_ttl_from_packet(nmb); + + /* + * We want to just add the new IP, as we now know the requesting + * machine claims to own it. But we can't just do that as an arbitrary + * amount of time may have taken place between the name query + * request and this response. So we check that + * the name still exists and is in the same state - if so + * we just add the extra IP and update the ttl. + */ + + namerec = find_name_on_subnet(subrec, question_name, FIND_ANY_NAME); + + if( (namerec == NULL) || (namerec->data.source != REGISTER_NAME) || !WINS_STATE_ACTIVE(namerec) ) { + DEBUG(3,("wins_multihomed_register_query_success: name %s is not in the correct state to add \ +a subsequent IP address.\n", nmb_namestr(question_name) )); + send_wins_name_registration_response(RFS_ERR, 0, orig_reg_packet); + + orig_reg_packet->locked = False; + free_packet(orig_reg_packet); + + return; + } + + if(!find_ip_in_name_record(namerec, from_ip)) { + add_ip_to_name_record(namerec, from_ip); + } + + get_global_id_and_update(&namerec->data.id, True); + update_wins_owner(namerec, our_fake_ip); + update_wins_flag(namerec, WINS_ACTIVE); + update_name_ttl(namerec, ttl); + wins_hook("add", namerec, ttl); + send_wins_name_registration_response(0, ttl, orig_reg_packet); + + orig_reg_packet->locked = False; + free_packet(orig_reg_packet); +} + +/*********************************************************************** + Deal with a name registration request query failure to a client that + owned the name. + + We have a locked pointer to the original packet stashed away in the + userdata pointer. +************************************************************************/ + +static void wins_multihomed_register_query_fail(struct subnet_record *subrec, + struct response_record *rrec, + struct nmb_name *question_name, + int rcode) +{ + struct userdata_struct *userdata = rrec->userdata; + struct packet_struct *orig_reg_packet; + + memcpy((char *)&orig_reg_packet, userdata->data, sizeof(struct packet_struct *)); + + DEBUG(3,("wins_multihomed_register_query_fail: Registering machine at IP %s failed to answer \ +query successfully for name %s.\n", inet_ntoa(orig_reg_packet->ip), nmb_namestr(question_name) )); + send_wins_name_registration_response(RFS_ERR, 0, orig_reg_packet); + + orig_reg_packet->locked = False; + free_packet(orig_reg_packet); + return; +} + +/*********************************************************************** + Deal with a multihomed name registration request to a WINS server. + These cannot be group name registrations. +***********************************************************************/ + +void wins_process_multihomed_name_registration_request( struct subnet_record *subrec, + struct packet_struct *p) +{ + struct nmb_packet *nmb = &p->packet.nmb; + struct nmb_name *question = &nmb->question.question_name; + bool bcast = nmb->header.nm_flags.bcast; + uint16_t nb_flags = get_nb_flags(nmb->additional->rdata); + int ttl = get_ttl_from_packet(nmb); + struct name_record *namerec = NULL; + struct in_addr from_ip; + bool group = (nb_flags & NB_GROUP) ? True : False; + struct in_addr our_fake_ip; + unstring qname; + + our_fake_ip = interpret_addr2("0.0.0.0"); + putip((char *)&from_ip,&nmb->additional->rdata[2]); + + if(bcast) { + /* + * We should only get unicast name registration packets here. + * Anyone trying to register broadcast should not be going to a WINS + * server. Log an error here. + */ + + DEBUG(0,("wins_process_multihomed_name_registration_request: broadcast name registration request \ +received for name %s from IP %s on subnet %s. Error - should not be sent to WINS server\n", + nmb_namestr(question), inet_ntoa(from_ip), subrec->subnet_name)); + return; + } + + /* + * Only unique names should be registered multihomed. + */ + + if(group) { + DEBUG(0,("wins_process_multihomed_name_registration_request: group name registration request \ +received for name %s from IP %s on subnet %s. Error - group names should not be multihomed.\n", + nmb_namestr(question), inet_ntoa(from_ip), subrec->subnet_name)); + return; + } + + DEBUG(3,("wins_process_multihomed_name_registration_request: name registration for name %s \ +IP %s\n", nmb_namestr(question), inet_ntoa(from_ip) )); + + /* + * Deal with policy regarding 0x1d names. + */ + + if(question->name_type == 0x1d) { + DEBUG(3,("wins_process_multihomed_name_registration_request: Ignoring request \ +to register name %s from IP %s.\n", nmb_namestr(question), inet_ntoa(p->ip) )); + send_wins_name_registration_response(0, ttl, p); + return; + } + + /* + * See if the name already exists. + */ + + namerec = find_name_on_subnet(subrec, question, FIND_ANY_NAME); + + /* + * if the record exists but NOT in active state, + * consider it dead. + */ + + if ((namerec != NULL) && !WINS_STATE_ACTIVE(namerec)) { + DEBUG(5,("wins_process_multihomed_name_registration_request: Name (%s) in WINS was not active - removing it.\n", nmb_namestr(question))); + remove_name_from_namelist(subrec, namerec); + namerec = NULL; + } + + /* + * Deal with the case where the name found was a dns entry. + * Remove it as we now have a NetBIOS client registering the + * name. + */ + + if( (namerec != NULL) && ( (namerec->data.source == DNS_NAME) || (namerec->data.source == DNSFAIL_NAME) ) ) { + DEBUG(5,("wins_process_multihomed_name_registration_request: Name (%s) in WINS was a dns lookup \ +- removing it.\n", nmb_namestr(question) )); + remove_name_from_namelist( subrec, namerec); + namerec = NULL; + } + + /* + * Reject if the name exists and is not a REGISTER_NAME. + * (ie. Don't allow any static names to be overwritten. + */ + + if( (namerec != NULL) && (namerec->data.source != REGISTER_NAME) ) { + DEBUG( 3, ( "wins_process_multihomed_name_registration_request: Attempt \ +to register name %s. Name already exists in WINS with source type %d.\n", + nmb_namestr(question), namerec->data.source )); + send_wins_name_registration_response(RFS_ERR, 0, p); + return; + } + + /* + * Reject if the name exists and is a GROUP name and is active. + */ + + if((namerec != NULL) && NAME_GROUP(namerec) && WINS_STATE_ACTIVE(namerec)) { + DEBUG(3,("wins_process_multihomed_name_registration_request: Attempt to register name %s. Name \ +already exists in WINS as a GROUP name.\n", nmb_namestr(question) )); + send_wins_name_registration_response(RFS_ERR, 0, p); + return; + } + + /* + * From here on down we know that if the name exists in the WINS db it is + * a unique name, not a group name. + */ + + /* + * If the name exists and is one of our names then check the + * registering IP address. If it's not one of ours then automatically + * reject without doing the query - we know we will reject it. + */ + + if((namerec != NULL) && (is_myname(namerec->name.name)) ) { + if(!ismyip_v4(from_ip)) { + DEBUG(3,("wins_process_multihomed_name_registration_request: Attempt to register name %s. Name \ +is one of our (WINS server) names. Denying registration.\n", nmb_namestr(question) )); + send_wins_name_registration_response(RFS_ERR, 0, p); + return; + } else { + /* + * It's one of our names and one of our IP's. Ensure the IP is in the record and + * update the ttl. Update the version ID to force replication. + */ + update_name_ttl(namerec, ttl); + + if(!find_ip_in_name_record(namerec, from_ip)) { + get_global_id_and_update(&namerec->data.id, True); + update_wins_owner(namerec, our_fake_ip); + update_wins_flag(namerec, WINS_ACTIVE); + + add_ip_to_name_record(namerec, from_ip); + } + + wins_hook("refresh", namerec, ttl); + send_wins_name_registration_response(0, ttl, p); + return; + } + } + + /* + * If the name exists and is active, check if the IP address is already registered + * to that name. If so then update the ttl and reply success. + */ + + if((namerec != NULL) && find_ip_in_name_record(namerec, from_ip) && WINS_STATE_ACTIVE(namerec)) { + update_name_ttl(namerec, ttl); + + /* + * If it's a replica, we need to become the wins owner + * to force the replication + */ + if (!ip_equal_v4(namerec->data.wins_ip, our_fake_ip)) { + get_global_id_and_update(&namerec->data.id, True); + update_wins_owner(namerec, our_fake_ip); + update_wins_flag(namerec, WINS_ACTIVE); + } + + wins_hook("refresh", namerec, ttl); + send_wins_name_registration_response(0, ttl, p); + return; + } + + /* + * If the name exists do a query to the owner + * to see if they still want the name. + */ + + if(namerec != NULL) { + long *ud[(sizeof(struct userdata_struct) + sizeof(struct packet_struct *))/sizeof(long *) + 1]; + struct userdata_struct *userdata = (struct userdata_struct *)ud; + + /* + * First send a WACK to the registering machine. + */ + + send_wins_wack_response(60, p); + + /* + * When the reply comes back we need the original packet. + * Lock this so it won't be freed and then put it into + * the userdata structure. + */ + + p->locked = True; + + userdata = (struct userdata_struct *)ud; + + userdata->copy_fn = NULL; + userdata->free_fn = NULL; + userdata->userdata_len = sizeof(struct packet_struct *); + memcpy(userdata->data, (char *)&p, sizeof(struct packet_struct *) ); + + /* + * Use the new call to send a query directly to an IP address. + * This sends the query directly to the IP address, and ensures + * the recursion desired flag is not set (you were right Luke :-). + * This function should *only* be called from the WINS server + * code. JRA. + * + * Note that this packet is sent to the current owner of the name, + * not the person who sent the packet + */ + + pull_ascii_nstring( qname, sizeof(qname), question->name); + query_name_from_wins_server( namerec->data.ip[0], + qname, + question->name_type, + wins_multihomed_register_query_success, + wins_multihomed_register_query_fail, + userdata ); + + return; + } + + /* + * Name did not exist - add it. + */ + + pull_ascii_nstring( qname, sizeof(qname), question->name); + add_name_to_subnet( subrec, qname, question->name_type, + nb_flags, ttl, REGISTER_NAME, 1, &from_ip); + + if ((namerec = find_name_on_subnet(subrec, question, FIND_ANY_NAME))) { + get_global_id_and_update(&namerec->data.id, True); + update_wins_owner(namerec, our_fake_ip); + update_wins_flag(namerec, WINS_ACTIVE); + wins_hook("add", namerec, ttl); + } + + send_wins_name_registration_response(0, ttl, p); +} + +/*********************************************************************** + Fetch all *<1b> names from the WINS db and store on the namelist. +***********************************************************************/ + +static int fetch_1b_traverse_fn(TDB_CONTEXT *tdb, TDB_DATA kbuf, TDB_DATA dbuf, void *state) +{ + struct name_record *namerec = NULL; + + if (kbuf.dsize != sizeof(unstring) + 1) { + return 0; + } + + /* Filter out all non-1b names. */ + if (kbuf.dptr[sizeof(unstring)] != 0x1b) { + return 0; + } + + namerec = wins_record_to_name_record(kbuf, dbuf); + if (!namerec) { + return 0; + } + + DLIST_ADD(wins_server_subnet->namelist, namerec); + return 0; +} + +void fetch_all_active_wins_1b_names(void) +{ + tdb_traverse(wins_tdb, fetch_1b_traverse_fn, NULL); +} + +/*********************************************************************** + Deal with the special name query for *<1b>. +***********************************************************************/ + +static void process_wins_dmb_query_request(struct subnet_record *subrec, + struct packet_struct *p) +{ + struct name_record *namerec = NULL; + char *prdata; + int num_ips; + + /* + * Go through all the ACTIVE names in the WINS db looking for those + * ending in <1b>. Use this to calculate the number of IP + * addresses we need to return. + */ + + num_ips = 0; + + /* First, clear the in memory list - we're going to re-populate + it with the tdb_traversal in fetch_all_active_wins_1b_names. */ + + wins_delete_all_tmp_in_memory_records(); + + fetch_all_active_wins_1b_names(); + + for( namerec = subrec->namelist; namerec; namerec = namerec->next ) { + if( WINS_STATE_ACTIVE(namerec) && namerec->name.name_type == 0x1b) { + num_ips += namerec->data.num_ips; + } + } + + if(num_ips == 0) { + /* + * There are no 0x1b names registered. Return name query fail. + */ + send_wins_name_query_response(NAM_ERR, p, NULL); + return; + } + + if((prdata = (char *)SMB_MALLOC( num_ips * 6 )) == NULL) { + DEBUG(0,("process_wins_dmb_query_request: Malloc fail !.\n")); + return; + } + + /* + * Go through all the names again in the WINS db looking for those + * ending in <1b>. Add their IP addresses into the list we will + * return. + */ + + num_ips = 0; + for( namerec = subrec->namelist; namerec; namerec = namerec->next ) { + if( WINS_STATE_ACTIVE(namerec) && namerec->name.name_type == 0x1b) { + int i; + for(i = 0; i < namerec->data.num_ips; i++) { + set_nb_flags(&prdata[num_ips * 6],namerec->data.nb_flags); + putip((char *)&prdata[(num_ips * 6) + 2], &namerec->data.ip[i]); + num_ips++; + } + } + } + + /* + * Send back the reply containing the IP list. + */ + + reply_netbios_packet(p, /* Packet to reply to. */ + 0, /* Result code. */ + WINS_QUERY, /* nmbd type code. */ + NMB_NAME_QUERY_OPCODE, /* opcode. */ + lp_min_wins_ttl(), /* ttl. */ + prdata, /* data to send. */ + num_ips*6); /* data length. */ + + SAFE_FREE(prdata); +} + +/**************************************************************************** +Send a WINS name query response. +**************************************************************************/ + +void send_wins_name_query_response(int rcode, struct packet_struct *p, + struct name_record *namerec) +{ + char rdata[6]; + char *prdata = rdata; + int reply_data_len = 0; + int ttl = 0; + int i; + + memset(rdata,'\0',6); + + if(rcode == 0) { + + int ip_count; + + ttl = (namerec->data.death_time != PERMANENT_TTL) ? namerec->data.death_time - p->timestamp : lp_max_wins_ttl(); + + /* The netbios reply packet data section is limited to 576 bytes. In theory + * this should give us space for 96 addresses, but in practice, 86 appears + * to be the max (don't know why). If we send any more than that, + * reply_netbios_packet will fail to send a reply to avoid a memcpy buffer + * overflow. Keep the count to 85 and it will be ok */ + ip_count=namerec->data.num_ips; + if(ip_count>85) { + ip_count=85; + } + + /* Copy all known ip addresses into the return data. */ + /* Optimise for the common case of one IP address so we don't need a malloc. */ + + if( ip_count == 1 ) { + prdata = rdata; + } else { + if((prdata = (char *)SMB_MALLOC( ip_count * 6 )) == NULL) { + DEBUG(0,("send_wins_name_query_response: malloc fail !\n")); + return; + } + } + + for(i = 0; i < ip_count; i++) { + set_nb_flags(&prdata[i*6],namerec->data.nb_flags); + putip((char *)&prdata[2+(i*6)], &namerec->data.ip[i]); + } + + sort_query_replies(prdata, i, p->ip); + reply_data_len = ip_count * 6; + } + + reply_netbios_packet(p, /* Packet to reply to. */ + rcode, /* Result code. */ + WINS_QUERY, /* nmbd type code. */ + NMB_NAME_QUERY_OPCODE, /* opcode. */ + ttl, /* ttl. */ + prdata, /* data to send. */ + reply_data_len); /* data length. */ + + if(prdata != rdata) { + SAFE_FREE(prdata); + } +} + +/*********************************************************************** + Deal with a name query. +***********************************************************************/ + +void wins_process_name_query_request(struct subnet_record *subrec, + struct packet_struct *p) +{ + struct nmb_packet *nmb = &p->packet.nmb; + struct nmb_name *question = &nmb->question.question_name; + struct name_record *namerec = NULL; + unstring qname; + + DEBUG(3,("wins_process_name_query: name query for name %s from IP %s\n", + nmb_namestr(question), inet_ntoa(p->ip) )); + + /* + * Special name code. If the queried name is *<1b> then search + * the entire WINS database and return a list of all the IP addresses + * registered to any <1b> name. This is to allow domain master browsers + * to discover other domains that may not have a presence on their subnet. + */ + + pull_ascii_nstring(qname, sizeof(qname), question->name); + if(strequal( qname, "*") && (question->name_type == 0x1b)) { + process_wins_dmb_query_request( subrec, p); + return; + } + + namerec = find_name_on_subnet(subrec, question, FIND_ANY_NAME); + + if(namerec != NULL) { + /* + * If the name is not anymore in active state then reply not found. + * it's fair even if we keep it in the cache for days. + */ + if (!WINS_STATE_ACTIVE(namerec)) { + DEBUG(3,("wins_process_name_query: name query for name %s - name expired. Returning fail.\n", + nmb_namestr(question) )); + send_wins_name_query_response(NAM_ERR, p, namerec); + return; + } + + /* + * If it's a DNSFAIL_NAME then reply name not found. + */ + + if( namerec->data.source == DNSFAIL_NAME ) { + DEBUG(3,("wins_process_name_query: name query for name %s returning DNS fail.\n", + nmb_namestr(question) )); + send_wins_name_query_response(NAM_ERR, p, namerec); + return; + } + + /* + * If the name has expired then reply name not found. + */ + + if( (namerec->data.death_time != PERMANENT_TTL) && (namerec->data.death_time < p->timestamp) ) { + DEBUG(3,("wins_process_name_query: name query for name %s - name expired. Returning fail.\n", + nmb_namestr(question) )); + send_wins_name_query_response(NAM_ERR, p, namerec); + return; + } + + DEBUG(3,("wins_process_name_query: name query for name %s returning first IP %s.\n", + nmb_namestr(question), inet_ntoa(namerec->data.ip[0]) )); + + send_wins_name_query_response(0, p, namerec); + return; + } + + /* + * Name not found in WINS - try a dns query if it's a 0x20 name. + */ + + if(lp_wins_dns_proxy() && ((question->name_type == 0x20) || question->name_type == 0)) { + DEBUG(3,("wins_process_name_query: name query for name %s not found - doing dns lookup.\n", + nmb_namestr(question) )); + + queue_dns_query(p, question); + return; + } + + /* + * Name not found - return error. + */ + + send_wins_name_query_response(NAM_ERR, p, NULL); +} + +/**************************************************************************** +Send a WINS name release response. +**************************************************************************/ + +static void send_wins_name_release_response(int rcode, struct packet_struct *p) +{ + struct nmb_packet *nmb = &p->packet.nmb; + char rdata[6]; + + memcpy(&rdata[0], &nmb->additional->rdata[0], 6); + + reply_netbios_packet(p, /* Packet to reply to. */ + rcode, /* Result code. */ + NMB_REL, /* nmbd type code. */ + NMB_NAME_RELEASE_OPCODE, /* opcode. */ + 0, /* ttl. */ + rdata, /* data to send. */ + 6); /* data length. */ +} + +/*********************************************************************** + Deal with a name release. +***********************************************************************/ + +void wins_process_name_release_request(struct subnet_record *subrec, + struct packet_struct *p) +{ + struct nmb_packet *nmb = &p->packet.nmb; + struct nmb_name *question = &nmb->question.question_name; + bool bcast = nmb->header.nm_flags.bcast; + uint16_t nb_flags = get_nb_flags(nmb->additional->rdata); + struct name_record *namerec = NULL; + struct in_addr from_ip; + bool releasing_group_name = (nb_flags & NB_GROUP) ? True : False; + + putip((char *)&from_ip,&nmb->additional->rdata[2]); + + if(bcast) { + /* + * We should only get unicast name registration packets here. + * Anyone trying to register broadcast should not be going to a WINS + * server. Log an error here. + */ + + DEBUG(0,("wins_process_name_release_request: broadcast name registration request \ +received for name %s from IP %s on subnet %s. Error - should not be sent to WINS server\n", + nmb_namestr(question), inet_ntoa(from_ip), subrec->subnet_name)); + return; + } + + DEBUG(3,("wins_process_name_release_request: %s name release for name %s \ +IP %s\n", releasing_group_name ? "Group" : "Unique", nmb_namestr(question), inet_ntoa(from_ip) )); + + /* + * Deal with policy regarding 0x1d names. + */ + + if(!releasing_group_name && (question->name_type == 0x1d)) { + DEBUG(3,("wins_process_name_release_request: Ignoring request \ +to release name %s from IP %s.\n", nmb_namestr(question), inet_ntoa(p->ip) )); + send_wins_name_release_response(0, p); + return; + } + + /* + * See if the name already exists. + */ + + namerec = find_name_on_subnet(subrec, question, FIND_ANY_NAME); + + if( (namerec == NULL) || ((namerec != NULL) && (namerec->data.source != REGISTER_NAME)) ) { + send_wins_name_release_response(NAM_ERR, p); + return; + } + + /* + * Check that the sending machine has permission to release this name. + * If it's a group name not ending in 0x1c then just say yes and let + * the group time out. + */ + + if(releasing_group_name && (question->name_type != 0x1c)) { + send_wins_name_release_response(0, p); + return; + } + + /* + * Check that the releasing node is on the list of IP addresses + * for this name. Disallow the release if not. + */ + + if(!find_ip_in_name_record(namerec, from_ip)) { + DEBUG(3,("wins_process_name_release_request: Refusing request to \ +release name %s as IP %s is not one of the known IP's for this name.\n", + nmb_namestr(question), inet_ntoa(from_ip) )); + send_wins_name_release_response(NAM_ERR, p); + return; + } + + /* + * Check if the record is active. IF it's already released + * or tombstoned, refuse the release. + */ + + if (!WINS_STATE_ACTIVE(namerec)) { + DEBUG(3,("wins_process_name_release_request: Refusing request to \ +release name %s as this record is not active anymore.\n", nmb_namestr(question) )); + send_wins_name_release_response(NAM_ERR, p); + return; + } + + /* + * Check if the record is a 0x1c group + * and has more then one ip + * remove only this address. + */ + + if(releasing_group_name && (question->name_type == 0x1c) && (namerec->data.num_ips > 1)) { + remove_ip_from_name_record(namerec, from_ip); + DEBUG(3,("wins_process_name_release_request: Remove IP %s from NAME: %s\n", + inet_ntoa(from_ip),nmb_namestr(question))); + wins_hook("delete", namerec, 0); + send_wins_name_release_response(0, p); + return; + } + + /* + * Send a release response. + * Flag the name as released and update the ttl + */ + + namerec->data.wins_flags |= WINS_RELEASED; + update_name_ttl(namerec, EXTINCTION_INTERVAL); + + wins_hook("delete", namerec, 0); + send_wins_name_release_response(0, p); +} + +/******************************************************************* + WINS time dependent processing. +******************************************************************/ + +static int wins_processing_traverse_fn(TDB_CONTEXT *tdb, TDB_DATA kbuf, TDB_DATA dbuf, void *state) +{ + time_t t = *(time_t *)state; + bool store_record = False; + struct name_record *namerec = NULL; + struct in_addr our_fake_ip; + + our_fake_ip = interpret_addr2("0.0.0.0"); + if (kbuf.dsize != sizeof(unstring) + 1) { + return 0; + } + + namerec = wins_record_to_name_record(kbuf, dbuf); + if (!namerec) { + return 0; + } + + if( (namerec->data.death_time != PERMANENT_TTL) && (namerec->data.death_time < t) ) { + if( namerec->data.source == SELF_NAME ) { + DEBUG( 3, ( "wins_processing_traverse_fn: Subnet %s not expiring SELF name %s\n", + wins_server_subnet->subnet_name, nmb_namestr(&namerec->name) ) ); + namerec->data.death_time += 300; + store_record = True; + goto done; + } else if (namerec->data.source == DNS_NAME || namerec->data.source == DNSFAIL_NAME) { + DEBUG(3,("wins_processing_traverse_fn: deleting timed out DNS name %s\n", + nmb_namestr(&namerec->name))); + remove_name_from_wins_namelist(namerec ); + goto done; + } + + /* handle records, samba is the wins owner */ + if (ip_equal_v4(namerec->data.wins_ip, our_fake_ip)) { + switch (namerec->data.wins_flags & WINS_STATE_MASK) { + case WINS_ACTIVE: + namerec->data.wins_flags&=~WINS_STATE_MASK; + namerec->data.wins_flags|=WINS_RELEASED; + namerec->data.death_time = t + EXTINCTION_INTERVAL; + DEBUG(3,("wins_processing_traverse_fn: expiring %s\n", + nmb_namestr(&namerec->name))); + store_record = True; + goto done; + case WINS_RELEASED: + namerec->data.wins_flags&=~WINS_STATE_MASK; + namerec->data.wins_flags|=WINS_TOMBSTONED; + namerec->data.death_time = t + EXTINCTION_TIMEOUT; + get_global_id_and_update(&namerec->data.id, True); + DEBUG(3,("wins_processing_traverse_fn: tombstoning %s\n", + nmb_namestr(&namerec->name))); + store_record = True; + goto done; + case WINS_TOMBSTONED: + DEBUG(3,("wins_processing_traverse_fn: deleting %s\n", + nmb_namestr(&namerec->name))); + remove_name_from_wins_namelist(namerec ); + goto done; + } + } else { + switch (namerec->data.wins_flags & WINS_STATE_MASK) { + case WINS_ACTIVE: + /* that's not as MS says it should be */ + namerec->data.wins_flags&=~WINS_STATE_MASK; + namerec->data.wins_flags|=WINS_TOMBSTONED; + namerec->data.death_time = t + EXTINCTION_TIMEOUT; + DEBUG(3,("wins_processing_traverse_fn: tombstoning %s\n", + nmb_namestr(&namerec->name))); + store_record = True; + goto done; + case WINS_TOMBSTONED: + DEBUG(3,("wins_processing_traverse_fn: deleting %s\n", + nmb_namestr(&namerec->name))); + remove_name_from_wins_namelist(namerec ); + goto done; + case WINS_RELEASED: + DEBUG(0,("wins_processing_traverse_fn: %s is in released state and\ +we are not the wins owner !\n", nmb_namestr(&namerec->name))); + goto done; + } + } + } + + done: + + if (store_record) { + wins_store_changed_namerec(namerec); + } + + SAFE_FREE(namerec->data.ip); + SAFE_FREE(namerec); + + return 0; +} + +/******************************************************************* + Time dependent wins processing. +******************************************************************/ + +void initiate_wins_processing(time_t t) +{ + static time_t lasttime = 0; + + if (!lasttime) { + lasttime = t; + } + if (t - lasttime < 20) { + return; + } + + if(!lp_we_are_a_wins_server()) { + lasttime = t; + return; + } + + tdb_traverse(wins_tdb, wins_processing_traverse_fn, &t); + + wins_delete_all_tmp_in_memory_records(); + + wins_write_database(t, True); + + lasttime = t; +} + +/******************************************************************* + Write out one record. +******************************************************************/ + +void wins_write_name_record(struct name_record *namerec, FILE *fp) +{ + int i; + struct tm *tm; + + DEBUGADD(4,("%-19s ", nmb_namestr(&namerec->name) )); + + if( namerec->data.death_time != PERMANENT_TTL ) { + char *ts, *nl; + + tm = localtime(&namerec->data.death_time); + if (!tm) { + return; + } + ts = asctime(tm); + if (!ts) { + return; + } + nl = strrchr( ts, '\n' ); + if( NULL != nl ) { + *nl = '\0'; + } + DEBUGADD(4,("TTL = %s ", ts )); + } else { + DEBUGADD(4,("TTL = PERMANENT ")); + } + + for (i = 0; i < namerec->data.num_ips; i++) { + DEBUGADD(4,("%15s ", inet_ntoa(namerec->data.ip[i]) )); + } + DEBUGADD(4,("%2x\n", namerec->data.nb_flags )); + + if( namerec->data.source == REGISTER_NAME ) { + unstring name; + pull_ascii_nstring(name, sizeof(name), namerec->name.name); + fprintf(fp, "\"%s#%02x\" %d ", name, + namerec->name.name_type, /* Ignore scope. */ + (int)namerec->data.death_time); + + for (i = 0; i < namerec->data.num_ips; i++) + fprintf(fp, "%s ", inet_ntoa(namerec->data.ip[i])); + fprintf(fp, "%2xR\n", namerec->data.nb_flags); + } +} + +/******************************************************************* + Write out the current WINS database. +******************************************************************/ + +static int wins_writedb_traverse_fn(TDB_CONTEXT *tdb, TDB_DATA kbuf, TDB_DATA dbuf, void *state) +{ + struct name_record *namerec = NULL; + FILE *fp = (FILE *)state; + + if (kbuf.dsize != sizeof(unstring) + 1) { + return 0; + } + + namerec = wins_record_to_name_record(kbuf, dbuf); + if (!namerec) { + return 0; + } + + wins_write_name_record(namerec, fp); + + SAFE_FREE(namerec->data.ip); + SAFE_FREE(namerec); + return 0; +} + + +void wins_write_database(time_t t, bool background) +{ + static time_t last_write_time = 0; + char *fname = NULL; + char *fnamenew = NULL; + + int fd; + FILE *fp; + + if (background) { + if (!last_write_time) { + last_write_time = t; + } + if (t - last_write_time < 120) { + return; + } + + } + + if(!lp_we_are_a_wins_server()) { + return; + } + + /* We will do the writing in a child process to ensure that the parent doesn't block while this is done */ + if (background) { + CatchChild(); + if (fork()) { + return; + } + if (tdb_reopen(wins_tdb)) { + DEBUG(0,("wins_write_database: tdb_reopen failed. Error was %s\n", + strerror(errno))); + _exit(0); + return; + } + } + + if (!(fname = state_path(talloc_tos(), WINS_LIST))) { + goto err_exit; + } + /* This is safe as the 0 length means "don't expand". */ + all_string_sub(fname,"//", "/", 0); + + if (asprintf(&fnamenew, "%s.%u", fname, (unsigned int)getpid()) < 0) { + goto err_exit; + } + + fd = open(fnamenew, O_WRONLY|O_CREAT, 0644); + if (fd == -1) { + DBG_ERR("Can't open %s: %s\n", fnamenew, strerror(errno)); + goto err_exit; + } + + fp = fdopen(fd, "w"); + if (fp == NULL) { + DBG_ERR("fdopen failed: %s\n", strerror(errno)); + close(fd); + goto err_exit; + } + fd = -1; + + DEBUG(4,("wins_write_database: Dump of WINS name list.\n")); + + fprintf(fp,"VERSION %d %u\n", WINS_VERSION, 0); + + tdb_traverse(wins_tdb, wins_writedb_traverse_fn, fp); + + fclose(fp); + chmod(fnamenew,0644); + unlink(fname); + rename(fnamenew,fname); + + err_exit: + + SAFE_FREE(fnamenew); + TALLOC_FREE(fname); + + if (background) { + _exit(0); + } +} + +#if 0 + Until winsrepl is done. +/**************************************************************************** + Process a internal Samba message receiving a wins record. +***************************************************************************/ + +void nmbd_wins_new_entry(struct messaging_context *msg, + void *private_data, + uint32_t msg_type, + struct server_id server_id, + DATA_BLOB *data) +{ + WINS_RECORD *record; + struct name_record *namerec = NULL; + struct name_record *new_namerec = NULL; + struct nmb_name question; + bool overwrite=False; + struct in_addr our_fake_ip; + int i; + + our_fake_ip = interpret_addr2("0.0.0.0"); + if (buf==NULL) { + return; + } + + /* Record should use UNIX codepage. Ensure this is so in the wrepld code. JRA. */ + record=(WINS_RECORD *)buf; + + make_nmb_name(&question, record->name, record->type); + + namerec = find_name_on_subnet(wins_server_subnet, &question, FIND_ANY_NAME); + + /* record doesn't exist, add it */ + if (namerec == NULL) { + DEBUG(3,("nmbd_wins_new_entry: adding new replicated record: %s<%02x> for wins server: %s\n", + record->name, record->type, inet_ntoa(record->wins_ip))); + + new_namerec=add_name_to_subnet( wins_server_subnet, + record->name, + record->type, + record->nb_flags, + EXTINCTION_INTERVAL, + REGISTER_NAME, + record->num_ips, + record->ip); + + if (new_namerec!=NULL) { + update_wins_owner(new_namerec, record->wins_ip); + update_wins_flag(new_namerec, record->wins_flags); + new_namerec->data.id=record->id; + + wins_server_subnet->namelist_changed = True; + } + } + + /* check if we have a conflict */ + if (namerec != NULL) { + /* both records are UNIQUE */ + if (namerec->data.wins_flags&WINS_UNIQUE && record->wins_flags&WINS_UNIQUE) { + + /* the database record is a replica */ + if (!ip_equal_v4(namerec->data.wins_ip, our_fake_ip)) { + if (namerec->data.wins_flags&WINS_ACTIVE && record->wins_flags&WINS_TOMBSTONED) { + if (ip_equal_v4(namerec->data.wins_ip, record->wins_ip)) + overwrite=True; + } else + overwrite=True; + } else { + /* we are the wins owner of the database record */ + /* the 2 records have the same IP address */ + if (ip_equal_v4(namerec->data.ip[0], record->ip[0])) { + if (namerec->data.wins_flags&WINS_ACTIVE && record->wins_flags&WINS_TOMBSTONED) + get_global_id_and_update(&namerec->data.id, True); + else + overwrite=True; + + } else { + /* the 2 records have different IP address */ + if (namerec->data.wins_flags&WINS_ACTIVE) { + if (record->wins_flags&WINS_TOMBSTONED) + get_global_id_and_update(&namerec->data.id, True); + if (record->wins_flags&WINS_ACTIVE) + /* send conflict challenge to the replica node */ + ; + } else + overwrite=True; + } + + } + } + + /* the replica is a standard group */ + if (record->wins_flags&WINS_NGROUP || record->wins_flags&WINS_SGROUP) { + /* if the database record is unique and active force a name release */ + if (namerec->data.wins_flags&WINS_UNIQUE) + /* send a release name to the unique node */ + ; + overwrite=True; + + } + + /* the replica is a special group */ + if (record->wins_flags&WINS_SGROUP && namerec->data.wins_flags&WINS_SGROUP) { + if (namerec->data.wins_flags&WINS_ACTIVE) { + for (i=0; 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_v4(record->wins_ip, namerec->data.wins_ip)) + overwrite=True; + + if (ip_equal_v4(namerec->data.wins_ip, our_fake_ip)) + if (namerec->data.wins_flags&WINS_UNIQUE) + get_global_id_and_update(&namerec->data.id, True); + + } + + if (record->wins_flags&WINS_ACTIVE && namerec->data.wins_flags&WINS_ACTIVE) + if (namerec->data.wins_flags&WINS_UNIQUE || + namerec->data.wins_flags&WINS_MHOMED) + if (ip_equal_v4(record->wins_ip, namerec->data.wins_ip)) + overwrite=True; + + } + + if (overwrite == False) + DEBUG(3, ("nmbd_wins_new_entry: conflict in adding record: %s<%02x> from wins server: %s\n", + record->name, record->type, inet_ntoa(record->wins_ip))); + else { + DEBUG(3, ("nmbd_wins_new_entry: replacing record: %s<%02x> from wins server: %s\n", + record->name, record->type, inet_ntoa(record->wins_ip))); + + /* remove the old record and add a new one */ + remove_name_from_namelist( wins_server_subnet, namerec ); + new_namerec=add_name_to_subnet( wins_server_subnet, record->name, record->type, record->nb_flags, + EXTINCTION_INTERVAL, REGISTER_NAME, record->num_ips, record->ip); + if (new_namerec!=NULL) { + update_wins_owner(new_namerec, record->wins_ip); + update_wins_flag(new_namerec, record->wins_flags); + new_namerec->data.id=record->id; + + wins_server_subnet->namelist_changed = True; + } + + wins_server_subnet->namelist_changed = True; + } + + } +} +#endif diff --git a/source3/nmbd/nmbd_workgroupdb.c b/source3/nmbd/nmbd_workgroupdb.c new file mode 100644 index 0000000..cd97efd --- /dev/null +++ b/source3/nmbd/nmbd_workgroupdb.c @@ -0,0 +1,327 @@ +/* + 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 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "../librpc/gen_ndr/svcctl.h" +#include "nmbd/nmbd.h" +#include "lib/util/string_wrappers.h" + +extern uint16_t 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; +} + +/**************************************************************************** + Copy name to unstring. Used by create_workgroup() and find_workgroup_on_subnet(). +**************************************************************************/ + +static void name_to_unstring(unstring unname, const char *name) +{ + nstring nname; + + errno = 0; + push_ascii_nstring(nname, name); + if (errno == E2BIG) { + unstring tname; + pull_ascii_nstring(tname, sizeof(tname), nname); + strlcpy(unname, tname, sizeof(nname)); + DEBUG(0,("name_to_nstring: workgroup name %s is too long. Truncating to %s\n", + name, tname)); + } else { + unstrcpy(unname, name); + } +} + +/**************************************************************************** + 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 = SMB_MALLOC_P(struct work_record)) == NULL) { + DEBUG(0,("create_workgroup: malloc fail !\n")); + return NULL; + } + memset((char *)work, '\0', sizeof(*work)); + + name_to_unstring(work->work_group, name); + + 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_v4(&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) { + DLIST_REMOVE(subrec->workgrouplist, work); + 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; + unstring un_name; + + DEBUG(4, ("find_workgroup_on_subnet: workgroup search for %s on subnet %s: ", + name, subrec->subnet_name)); + + name_to_unstring(un_name, name); + + for (ret = subrec->workgrouplist; ret; ret = ret->next) { + if (strequal(ret->work_group,un_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) +{ + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); + 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_server_string(talloc_tos(), lp_sub), 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 == (time_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); + } + } + } +} diff --git a/source3/nmbd/wscript_build b/source3/nmbd/wscript_build new file mode 100644 index 0000000..399cdb4 --- /dev/null +++ b/source3/nmbd/wscript_build @@ -0,0 +1,45 @@ +#!/usr/bin/env python + +nmbd_cflags = '' +if bld.CONFIG_SET('HAVE_WNO_ERROR_STRINGOP_OVERFLOW'): + nmbd_cflags = '-Wno-error=stringop-overflow' + +bld.SAMBA3_BINARY('nmbd', + source=''' + asyncdns.c + nmbd.c + nmbd_become_dmb.c + nmbd_become_lmb.c + nmbd_browserdb.c + nmbd_browsesync.c + nmbd_elections.c + nmbd_incomingdgrams.c + nmbd_incomingrequests.c + nmbd_lmhosts.c + nmbd_logonnames.c + nmbd_mynames.c + nmbd_namelistdb.c + nmbd_namequery.c + nmbd_nameregister.c + nmbd_namerelease.c + nmbd_nodestatus.c + nmbd_packets.c + nmbd_processlogon.c + nmbd_responserecordsdb.c + nmbd_sendannounce.c + nmbd_serverlistdb.c + nmbd_subnetdb.c + nmbd_winsproxy.c + nmbd_winsserver.c + nmbd_workgroupdb.c + nmbd_synclists.c + ''', + cflags=nmbd_cflags, + deps=''' + talloc + tevent + smbconf + libsmb + CMDLINE_S3 + ''', + install_path='${SBINDIR}') |