/* 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 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, lp_workgroup()) && !AM_MASTER(work)) { if (lp_preferred_master()) { /* preferred master - not a master browser. force becoming a master browser, hence the log message. */ DEBUG(0,("%s preferred 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 ******************************************************************/ 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, lp_workgroup())) { 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 { /* 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); } } /**************************************************************************** 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, lp_workgroup()) && name_type == 0x1d) { remove_type_local |= SV_TYPE_MASTER_BROWSER; } if (AM_DOMMST(work) && strequal(name, lp_workgroup()) && name_type == 0x1b) { remove_type_domain |= SV_TYPE_DOMAIN_MASTER; } if (AM_DOMMEM(work) && strequal(name, lp_workgroup()) && 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, lp_workgroup(), 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, lp_workgroup()) && name_type == 0x1d) { add_type_local = True; } if (strequal(name, lp_workgroup()) && name_type == 0x1b) { add_type_domain = True; } if (strequal(name, lp_workgroup()) && 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; 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,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,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... */ { DEBUG(3,("2nd stage complete: registered as master browser\n")); 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,0,lp_serverstring(),True); if (work->serverlist == NULL) /* no servers! */ { /* ask all servers on our local net to announce to us */ /* XXXX OOPS! add_server_entry will always add one entry - our own. */ announce_request(work, d->bcast_ip); } /* If we have WINS support or are a WINS server we must add the workgroup we just became master browser for to the WINS subnet. This is needed so we have somewhere to save server lists when we do browser synchronization. */ if(wins_subnet != 0) { if(find_workgroupstruct(wins_subnet, work->work_group, True) == 0) DEBUG(0, ("become_local_master: \ Failed to add workgroup %s to WINS subnet.\n", work->work_group)); else DEBUG(3, ("become_local_master: Added workgroup %s to WINS subnet.\n", work->work_group)); /* Reset the announce master timer so that we do an announce as soon as possible now we are a master. */ reset_announce_timer(); } 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; 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... */ { if (lp_domain_master()) { DEBUG(3,("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, lp_workgroup(),0x1b,nb_type|NB_ACTIVE|NB_GROUP); /* DON'T do anything else after calling add_my_name_entry() */ break; } else { DEBUG(4,("samba not configured as a domain master.\n")); } break; } case DOMAIN_WAIT: { if (lp_domain_master()) { work->dom_state = DOMAIN_MST; /* ... become domain master */ DEBUG(3,("domain 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,0, lp_serverstring(),True); DEBUG(4,("samba is now a domain master\n")); break; } else { DEBUG(4,("samba not configured as a domain master.\n")); } 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; 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... */ { if (lp_domain_logons()) { 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, lp_workgroup(),0x1c,nb_type|NB_ACTIVE|NB_GROUP); /* DON'T do anything else after calling add_my_name_entry() */ break; } { DEBUG(4,("samba not configured as a logon master.\n")); } break; } case LOGON_WAIT: { if (lp_domain_logons()) { 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,0, lp_serverstring(),True); /* DON'T do anything else after calling add_my_name_entry() */ break; } else { DEBUG(4,("samba not configured as a logon server.\n")); } 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. */ announce_server(d, work, work->work_group, myname, 0, 0); remove_name_entry(d,work->work_group,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; 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(3,("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, lp_workgroup())) 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; }