/* Unix SMB/Netbios implementation. Version 1.9. NBT netbios routines and daemon - version 2 Copyright (C) Andrew Tridgell 1994-1997 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. Module name: nameelect.c Revision History: 14 jan 96: lkcl@pires.co.uk added multiple workgroup domain master support 04 jul 96: lkcl@pires.co.uk added system to become a master browser by stages. */ #include "includes.h" extern int ClientNMB; extern int ClientDGRAM; extern int DEBUGLEVEL; extern pstring scope; extern pstring myname; extern fstring myworkgroup; extern struct in_addr ipzero; extern struct in_addr wins_ip; /* here are my election parameters */ extern time_t StartupTime; extern struct subnet_record *subnetlist; extern uint16 nb_type; /* samba's NetBIOS name type */ /******************************************************************* occasionally check to see if the master browser is around ******************************************************************/ void check_master_browser(time_t t) { static time_t lastrun=0; struct subnet_record *d; if (!lastrun) lastrun = t; if (t < lastrun + CHECK_TIME_MST_BROWSE * 60) return; lastrun = t; dump_workgroups(); for (d = FIRST_SUBNET; d; d = NEXT_SUBNET_EXCLUDING_WINS(d)) { struct work_record *work; for (work = d->workgrouplist; work; work = work->next) { if (strequal(work->work_group, myworkgroup) && !AM_MASTER(work)) { if (lp_local_master() && lp_preferred_master()) { /* potential master browser - not a master browser. force becoming a master browser, hence the log message. */ DEBUG(2,("%s potential master for %s %s - force election\n", timestring(), work->work_group, inet_ntoa(d->bcast_ip))); browser_gone(work->work_group, d->bcast_ip); } else { /* if we are not the browse master of a workgroup, and we can't find a browser on the subnet, do something about it. */ queue_netbios_packet(d,ClientNMB,NMB_QUERY,NAME_QUERY_MST_CHK, work->work_group,0x1d,0,0,0,NULL,NULL, True,False,d->bcast_ip,d->bcast_ip); } } } } } /******************************************************************* what to do if a master browser DOESN't exist. option 1: force an election, and participate in it option 2: force an election, and let everyone else participate. ******************************************************************/ void browser_gone(char *work_name, struct in_addr ip) { struct subnet_record *d = find_subnet(ip); struct work_record *work = find_workgroupstruct(d, work_name, False); /* i don't know about this workgroup, therefore i don't care */ if (!work || !d) return; /* don't do election stuff on the WINS subnet */ if (ip_equal(d->bcast_ip,wins_ip)) return; if (strequal(work->work_group, myworkgroup)) { if (lp_local_master()) { /* we have discovered that there is no local master browser, and we are configured to initiate an election under exactly such circumstances. */ DEBUG(2,("Forcing election on %s %s\n", work->work_group,inet_ntoa(d->bcast_ip))); /* we can attempt to become master browser */ 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. */ /* local interfaces: force an election */ send_election(d, work->work_group, 0, 0, myname); /* only removes workgroup completely on a local interface persistent lmhosts entries on a local interface _will_ be removed). */ remove_workgroup(d, work,True); add_workgroup_to_subnet(d, work->work_group); } } } /**************************************************************************** send an election packet **************************************************************************/ void send_election(struct subnet_record *d, char *group,uint32 criterion, int timeup,char *name) { pstring outbuf; char *p; if (!d) return; DEBUG(2,("Sending election to %s for workgroup %s\n", inet_ntoa(d->bcast_ip),group)); bzero(outbuf,sizeof(outbuf)); p = outbuf; CVAL(p,0) = ANN_Election; /* election */ p++; CVAL(p,0) = (criterion == 0 && timeup == 0) ? 0 : ELECTION_VERSION; SIVAL(p,1,criterion); SIVAL(p,5,timeup*1000); /* ms - despite the spec */ p += 13; strcpy(p,name); strupper(p); p = skip_string(p,1); send_mailslot_reply(False,BROWSE_MAILSLOT,ClientDGRAM, outbuf,PTR_DIFF(p,outbuf), name,group,0,0x1e,d->bcast_ip,*iface_ip(d->bcast_ip)); } /**************************************************************************** un-register a SELF name that got rejected. if this name happens to be rejected when samba is in the process of becoming a master browser (registering __MSBROWSE__, WORKGROUP(1d) or WORKGROUP(1b)) then we must stop being a master browser. sad. **************************************************************************/ void name_unregister_work(struct subnet_record *d, char *name, int name_type) { struct work_record *work; int remove_type_local = 0; int remove_type_domain = 0; int remove_type_logon = 0; remove_netbios_name(d,name,name_type,SELF,ipzero); if (!(work = find_workgroupstruct(d, name, False))) return; /* work out what to unbecome, from the name type being removed */ if (ms_browser_name(name, name_type)) { remove_type_local |= SV_TYPE_MASTER_BROWSER; } if (AM_MASTER(work) && strequal(name, myworkgroup) && name_type == 0x1d) { remove_type_local |= SV_TYPE_MASTER_BROWSER; } if (AM_DOMMST(work) && strequal(name, myworkgroup) && name_type == 0x1b) { remove_type_domain |= SV_TYPE_DOMAIN_MASTER; } if (AM_DOMMEM(work) && strequal(name, myworkgroup) && name_type == 0x1c) { remove_type_logon|= SV_TYPE_DOMAIN_MEMBER; } if (remove_type_local ) unbecome_local_master (d, work, remove_type_local ); if (remove_type_domain) unbecome_domain_master(d, work, remove_type_domain); if (remove_type_logon ) unbecome_logon_server (d, work, remove_type_logon ); } /**************************************************************************** registers a name. if the name being added is a SELF name, we must additionally check whether to proceed to the next stage in samba becoming a master browser. **************************************************************************/ void name_register_work(struct subnet_record *d, char *name, int name_type, int nb_flags, time_t ttl, struct in_addr ip, BOOL bcast) { enum name_source source = (ismyip(ip) || ip_equal(ip, ipzero)) ? SELF : REGISTER; if (source == SELF) { struct work_record *work = find_workgroupstruct(d, myworkgroup, False); add_netbios_entry(d,name,name_type,nb_flags,ttl,source,ip,True,!bcast); if (work) { int add_type_local = False; int add_type_domain = False; int add_type_logon = False; DEBUG(4,("checking next stage: name_register_work %s\n", name)); /* work out what to become, from the name type being added */ if (ms_browser_name(name, name_type)) { add_type_local = True; } if (strequal(name, myworkgroup) && name_type == 0x1d) { add_type_local = True; } if (strequal(name, myworkgroup) && name_type == 0x1b) { add_type_domain = True; } if (strequal(name, myworkgroup) && name_type == 0x1c) { add_type_logon = True; } if (add_type_local ) become_local_master (d, work); if (add_type_domain) become_domain_master(d, work); if (add_type_logon ) become_logon_server (d, work); } } } /******************************************************************* become the local master browser. this is done in stages. note that this could take a while, particularly on a broadcast subnet, as we have to wait for the implicit registration of each name to be accepted. as each name is successfully registered, become_local_master() is called again, in order to initiate the next stage. see dead_netbios_entry() - deals with implicit name registration and response_name_reg() - deals with explicit registration with a WINS server. stage 1: was MST_POTENTIAL - go to MST_POTENTIAL and register ^1^2__MSBROWSE__^2^1. stage 2: was MST_BACK - go to MST_MSB and register WORKGROUP(0x1d) stage 3: was MST_MSB - go to MST_BROWSER and stay there XXXX note: this code still does not cope with the distinction between different types of nodes, particularly between M and P nodes. that comes later. ******************************************************************/ void become_local_master(struct subnet_record *d, struct work_record *work) { /* domain type must be limited to domain enum + server type. it must not have SV_TYPE_SERVER or anything else with SERVER in it, else clients get confused and start thinking this entry is a server not a workgroup */ uint32 domain_type = SV_TYPE_DOMAIN_ENUM|SV_TYPE_NT; if (!work || !d) return; if (!lp_local_master()) { DEBUG(0,("Samba not configured as a local master browser.\n")); return; } DEBUG(2,("Becoming master for %s %s (currently at stage %d)\n", work->work_group,inet_ntoa(d->bcast_ip),work->mst_state)); switch (work->mst_state) { case MST_POTENTIAL: /* while we were nothing but a server... */ { DEBUG(3,("go to first stage: register ^1^2__MSBROWSE__^2^1\n")); work->mst_state = MST_BACK; /* an election win was successful */ work->ElectionCriterion |= 0x5; /* update our server status */ work->ServerType &= ~SV_TYPE_POTENTIAL_BROWSER; add_server_entry(d,work,myname,work->ServerType|SV_TYPE_LOCAL_LIST_ONLY, 0,lp_serverstring(),True); /* add special browser name */ add_my_name_entry(d,MSBROWSE,0x01,nb_type|NB_ACTIVE|NB_GROUP); /* DON'T do anything else after calling add_my_name_entry() */ break; } case MST_BACK: /* while nothing had happened except we won an election... */ { DEBUG(3,("go to second stage: register as master browser\n")); work->mst_state = MST_MSB; /* registering MSBROWSE was successful */ /* add server entry on successful registration of MSBROWSE */ add_server_entry(d,work,work->work_group,domain_type|SV_TYPE_LOCAL_LIST_ONLY, 0,myname,True); /* add master name */ add_my_name_entry(d,work->work_group,0x1d,nb_type|NB_ACTIVE); /* DON'T do anything else after calling add_my_name_entry() */ break; } case MST_MSB: /* while we were still only registered MSBROWSE state... */ { int i = 0; struct server_record *sl; DEBUG(3,("2nd stage complete: registered as master browser for workgroup %s \ on subnet %s\n", work->work_group, inet_ntoa(d->bcast_ip))); work->mst_state = MST_BROWSER; /* registering WORKGROUP(1d) succeeded */ /* update our server status */ work->ServerType |= SV_TYPE_MASTER_BROWSER; DEBUG(3,("become_local_master: updating our server %s to type %x\n", myname, work->ServerType)); add_server_entry(d,work,myname,work->ServerType|SV_TYPE_LOCAL_LIST_ONLY, 0,lp_serverstring(),True); /* 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 */ announce_request(work, d->bcast_ip); } /* Reset the announce master timer so that we do an announce as soon as possible now we are a master. */ reset_announce_timer(); DEBUG(0,("Samba is now a local master browser for workgroup %s on subnet %s\n", work->work_group, inet_ntoa(d->bcast_ip))); break; } case MST_BROWSER: { /* don't have to do anything: just report success */ DEBUG(3,("3rd stage: become master browser!\n")); break; } } } /******************************************************************* become the domain master browser. this is done in stages. note that this could take a while, particularly on a broadcast subnet, as we have to wait for the implicit registration of each name to be accepted. as each name is successfully registered, become_domain_master() is called again, in order to initiate the next stage. see dead_netbios_entry() - deals with implicit name registration and response_name_reg() - deals with explicit registration with a WINS server. stage 1: was DOMAIN_NONE - go to DOMAIN_MST XXXX note: this code still does not cope with the distinction between different types of nodes, particularly between M and P nodes. that comes later. ******************************************************************/ void become_domain_master(struct subnet_record *d, struct work_record *work) { /* domain type must be limited to domain enum + server type. it must not have SV_TYPE_SERVER or anything else with SERVER in it, else clients get confused and start thinking this entry is a server not a workgroup */ if (!work || !d) return; if (!lp_domain_master()) { DEBUG(0,("Samba not configured as a domain master browser.\n")); return; } DEBUG(2,("Becoming domain master for %s %s (currently at stage %d)\n", work->work_group,inet_ntoa(d->bcast_ip),work->dom_state)); switch (work->dom_state) { case DOMAIN_NONE: /* while we were nothing but a server... */ { DEBUG(3,("become_domain_master: go to first stage: register <1b> name\n")); work->dom_state = DOMAIN_WAIT; /* XXXX the 0x1b is domain master browser name */ add_my_name_entry(d, work->work_group,0x1b,nb_type|NB_ACTIVE); /* DON'T do anything else after calling add_my_name_entry() */ break; } case DOMAIN_WAIT: { work->dom_state = DOMAIN_MST; /* ... become domain master */ DEBUG(3,("become_domain_master: first stage - register as domain member\n")); /* update our server status */ work->ServerType |= SV_TYPE_NT|SV_TYPE_DOMAIN_MASTER; add_server_entry(d,work,myname,work->ServerType|SV_TYPE_LOCAL_LIST_ONLY, 0, lp_serverstring(),True); DEBUG(0,("Samba is now a domain master browser for workgroup %s on subnet %s\n", work->work_group, inet_ntoa(d->bcast_ip))); if (d == wins_subnet) { /* ok! 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 DOMAIN.txt. */ add_domain_master_bcast(); } break; } case DOMAIN_MST: { /* don't have to do anything: just report success */ DEBUG(3,("domain second stage: there isn't one!\n")); break; } } } /******************************************************************* become a logon server. ******************************************************************/ void become_logon_server(struct subnet_record *d, struct work_record *work) { if (!work || !d) return; if (!lp_domain_logons()) { DEBUG(0,("samba not configured as a logon master.\n")); return; } DEBUG(2,("Becoming logon server for %s %s (currently at stage %d)\n", work->work_group,inet_ntoa(d->bcast_ip),work->log_state)); switch (work->log_state) { case LOGON_NONE: /* while we were nothing but a server... */ { DEBUG(3,("go to first stage: register <1c> name\n")); work->log_state = LOGON_WAIT; /* XXXX the 0x1c is apparently something to do with domain logons */ add_my_name_entry(d, myworkgroup,0x1c,nb_type|NB_ACTIVE|NB_GROUP); /* DON'T do anything else after calling add_my_name_entry() */ break; } case LOGON_WAIT: { work->log_state = LOGON_SRV; /* ... become logon server */ DEBUG(3,("logon second stage: register \n")); /* update our server status */ work->ServerType |= SV_TYPE_NT|SV_TYPE_DOMAIN_MEMBER; add_server_entry(d,work,myname,work->ServerType|SV_TYPE_LOCAL_LIST_ONLY ,0, lp_serverstring(),True); /* DON'T do anything else after calling add_my_name_entry() */ break; } case LOGON_SRV: { DEBUG(3,("logon third stage: there isn't one!\n")); break; } } } /******************************************************************* unbecome the local master browser. initates removal of necessary netbios names, and tells the world that we are no longer a master browser. XXXX this _should_ be used to demote to a backup master browser, without going straight to non-master browser. another time. ******************************************************************/ void unbecome_local_master(struct subnet_record *d, struct work_record *work, int remove_type) { int new_server_type = work->ServerType; /* can only remove master types with this function */ remove_type &= SV_TYPE_MASTER_BROWSER; new_server_type &= ~remove_type; if (remove_type) { DEBUG(2,("Becoming local non-master for %s\n",work->work_group)); /* no longer a master browser of any sort */ work->ServerType |= SV_TYPE_POTENTIAL_BROWSER; work->ElectionCriterion &= ~0x4; work->mst_state = MST_POTENTIAL; /* announce ourselves as no longer active as a master browser. */ announce_server(d, work, work->work_group, myname, 0, 0); remove_name_entry(d,MSBROWSE ,0x01); remove_name_entry(d,work->work_group,0x1d); } } /******************************************************************* unbecome the domain master browser. initates removal of necessary netbios names, and tells the world that we are no longer a domain browser. ******************************************************************/ void unbecome_domain_master(struct subnet_record *d, struct work_record *work, int remove_type) { int new_server_type = work->ServerType; DEBUG(2,("Becoming domain non-master for %s\n",work->work_group)); /* can only remove master or domain types with this function */ remove_type &= SV_TYPE_DOMAIN_MASTER; new_server_type &= ~remove_type; if (remove_type) { /* no longer a domain master browser of any sort */ work->dom_state = DOMAIN_NONE; /* announce ourselves as no longer active as a master browser on all our local subnets. */ for (d = FIRST_SUBNET; d; d = NEXT_SUBNET_EXCLUDING_WINS(d)) { work = find_workgroupstruct(d, myworkgroup, False); announce_server(d, work, work->work_group, myname, 0, 0); /* Remove the name entry without any NetBIOS traffic as that's how it was registered. */ remove_name_entry(d,work->work_group,0x1b); } /* Unregister the 1b name from the WINS server. */ if(wins_subnet != NULL) remove_name_entry(wins_subnet, myworkgroup, 0x1b); } } /******************************************************************* unbecome the logon server. initates removal of necessary netbios names, and tells the world that we are no longer a logon server. ******************************************************************/ void unbecome_logon_server(struct subnet_record *d, struct work_record *work, int remove_type) { int new_server_type = work->ServerType; DEBUG(2,("Becoming logon non-server for %s\n",work->work_group)); /* can only remove master or domain types with this function */ remove_type &= SV_TYPE_DOMAIN_MEMBER; new_server_type &= ~remove_type; if (remove_type) { /* no longer a master browser of any sort */ work->log_state = LOGON_NONE; /* announce ourselves as no longer active as a master browser. */ announce_server(d, work, work->work_group, myname, 0, 0); remove_name_entry(d,work->work_group,0x1c); } } /******************************************************************* run the election ******************************************************************/ void run_elections(time_t t) { static time_t lastime = 0; struct subnet_record *d; /* send election packets once a second */ if (lastime && t-lastime <= 0) return; lastime = t; for (d = FIRST_SUBNET; d; d = NEXT_SUBNET_EXCLUDING_WINS(d)) { struct work_record *work; for (work = d->workgrouplist; work; work = work->next) { if (work->RunningElection) { send_election(d,work->work_group, work->ElectionCriterion, t-StartupTime,myname); if (work->ElectionCount++ >= 4) { /* I won! now what :-) */ DEBUG(2,(">>> Won election on %s %s <<<\n", work->work_group,inet_ntoa(d->bcast_ip))); work->RunningElection = False; work->mst_state = MST_POTENTIAL; become_local_master(d, work); } } } } } /******************************************************************* work out if I win an election ******************************************************************/ static BOOL win_election(struct work_record *work,int version,uint32 criterion, int timeup,char *name) { int mytimeup = time(NULL) - StartupTime; uint32 mycriterion = work->ElectionCriterion; /* If local master is false then never win in election broadcasts. */ if(!lp_local_master()) { DEBUG(3,("win_election: Losing election as local master == False\n")); return False; } DEBUG(4,("election comparison: %x:%x %x:%x %d:%d %s:%s\n", version,ELECTION_VERSION, criterion,mycriterion, timeup,mytimeup, name,myname)); 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(myname,name) > 0) return(False); return(True); } /******************************************************************* process a election packet An election dynamically decides who will be the master. ******************************************************************/ void process_election(struct packet_struct *p,char *buf) { struct dgram_packet *dgram = &p->packet.dgram; struct in_addr ip = dgram->header.source_ip; struct subnet_record *d = find_subnet(ip); int version = CVAL(buf,0); uint32 criterion = IVAL(buf,1); int timeup = IVAL(buf,5)/1000; char *name = buf+13; struct work_record *work; if (!d) return; if (ip_equal(d->bcast_ip,wins_ip)) { DEBUG(0,("Unexpected election request from %s %s on WINS net\n", name, inet_ntoa(p->ip))); return; } name[15] = 0; DEBUG(3,("Election request from %s %s vers=%d criterion=%08x timeup=%d\n", name,inet_ntoa(p->ip),version,criterion,timeup)); if (same_context(dgram)) return; for (work = d->workgrouplist; work; work = work->next) { if (!strequal(work->work_group, myworkgroup)) continue; if (win_election(work, version,criterion,timeup,name)) { if (!work->RunningElection) { work->needelection = True; work->ElectionCount=0; work->mst_state = MST_POTENTIAL; } } else { work->needelection = False; if (work->RunningElection || AM_MASTER(work)) { work->RunningElection = False; DEBUG(3,(">>> Lost election on %s %s <<<\n", work->work_group,inet_ntoa(d->bcast_ip))); if (AM_MASTER(work)) { unbecome_local_master(d, work, SV_TYPE_MASTER_BROWSER); } } } } } /**************************************************************************** checks whether a browser election is to be run on any workgroup this function really ought to return the time between election packets (which depends on whether samba intends to be a domain master or a master browser) in milliseconds. ***************************************************************************/ BOOL check_elections(void) { struct subnet_record *d; BOOL run_any_election = False; for (d = FIRST_SUBNET; d; d = NEXT_SUBNET_EXCLUDING_WINS(d)) { struct work_record *work; for (work = d->workgrouplist; work; work = work->next) { run_any_election |= work->RunningElection; if (work->needelection && !work->RunningElection) { DEBUG(3,(">>> Starting election on %s %s <<<\n", work->work_group,inet_ntoa(d->bcast_ip))); work->ElectionCount = 0; work->RunningElection = True; work->needelection = False; } } } return run_any_election; }