/* Unix SMB/Netbios implementation. Version 1.9. NBT netbios routines and daemon - version 2 Copyright (C) Andrew Tridgell 1994-1996 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 ipgrp; /* 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 = subnetlist; d; d = d->next) { struct work_record *work; for (work = d->workgrouplist; work; work = work->next) { /* if we are not the browse master of a workgroup, and we can't find a browser on the subnet, do something about it. */ if (!AM_MASTER(work)) { 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,ipgrp)) 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(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; remove_netbios_name(d,name,name_type,SELF,ipzero); if (!(work = find_workgroupstruct(d, name, False))) return; if (ms_browser_name(name, name_type) || (AM_MASTER(work) && strequal(name, lp_workgroup()) == 0 && (name_type == 0x1d || name_type == 0x1b))) { int remove_type = 0; if (ms_browser_name(name, name_type)) remove_type = SV_TYPE_MASTER_BROWSER|SV_TYPE_DOMAIN_MASTER; if (name_type == 0x1d) remove_type = SV_TYPE_MASTER_BROWSER; if (name_type == 0x1b) remove_type = SV_TYPE_DOMAIN_MASTER; become_nonmaster(d, work, remove_type); } } /**************************************************************************** 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) { if (work->state != MST_NONE) { /* samba is in the process of working towards master browser-ness. initiate the next stage. */ become_master(d, work); return; } } } } /******************************************************************* become the 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_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_NONE - go to MST_NONE and register ^1^2__MSBROWSE__^2^1. stage 2: was MST_WON - go to MST_MSB and register WORKGROUP(0x1d) stage 3: was MST_MSB - go to MST_BROWSER and register WORKGROUP(0x1b) stage 4: was MST_BROWSER - go to MST_DOMAIN (do not pass GO, do not...) 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_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) return; DEBUG(2,("Becoming master for %s %s (currently at stage %d)\n", work->work_group,inet_ntoa(d->bcast_ip),work->state)); switch (work->state) { case MST_NONE: /* while we were nothing but a server... */ { DEBUG(3,("go to first stage: register ^1^2__MSBROWSE__^2^1\n")); work->state = MST_WON; /* ... 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() */ return; } case MST_WON: /* while nothing had happened except we won an election... */ { DEBUG(3,("go to second stage: register as master browser\n")); work->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() */ return; } case MST_MSB: /* while we were still only registered MSBROWSE state... */ { DEBUG(3,("2nd stage complete: registered as master browser\n")); work->state = MST_BROWSER; /* ... registering WORKGROUP(1d) succeeded */ /* update our server status */ work->ServerType |= SV_TYPE_MASTER_BROWSER; 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 */ announce_request(work, 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; } case MST_DOMAIN_NONE: { if (lp_domain_master()) { work->state = MST_DOMAIN_MEM; /* ... become domain member */ DEBUG(3,("domain first stage: register as domain member\n")); /* add domain member name */ add_my_name_entry(d,work->work_group,0x1e,nb_type|NB_ACTIVE|NB_GROUP); /* DON'T do anything else after calling add_my_name_entry() */ return; } else { DEBUG(4,("samba not configured as a domain master.\n")); } break; } case MST_DOMAIN_MEM: { if (lp_domain_master()) { work->state = MST_DOMAIN_TST; /* ... possibly become domain master */ DEBUG(3,("domain second stage: register as domain master\n")); if (lp_domain_logons()) { work->ServerType |= SV_TYPE_DOMAIN_MEMBER; add_server_entry(d,work,myname,work->ServerType,0,lp_serverstring(),True); } /* add domain master 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() */ return; } else { DEBUG(4,("samba not configured as a domain master.\n")); } break; } case MST_DOMAIN_TST: /* while we were still a master browser... */ { /* update our server status */ if (lp_domain_master()) { struct subnet_record *d1; uint32 update_type = 0; DEBUG(3,("domain third stage: samba is now a domain master.\n")); work->state = MST_DOMAIN; /* ... registering WORKGROUP(1b) succeeded */ update_type |= DFLT_SERVER_TYPE | SV_TYPE_DOMAIN_MASTER | SV_TYPE_POTENTIAL_BROWSER; work->ServerType |= update_type; add_server_entry(d,work,myname,work->ServerType,0,lp_serverstring(),True); for (d1 = subnetlist; d1; d1 = d1->next) { struct work_record *w; if (ip_equal(d1->bcast_ip, d->bcast_ip)) continue; for (w = d1->workgrouplist; w; w = w->next) { struct server_record *s = find_server(w, myname); if (strequal(w->work_group, work->work_group)) { w->ServerType |= update_type; } if (s) { s->serv.type |= update_type; DEBUG(4,("found server %s on %s: update to %8x\n", s->serv.name, inet_ntoa(d1->bcast_ip), s->serv.type)); } } } } break; } case MST_DOMAIN: { /* don't have to do anything: just report success */ DEBUG(3,("fifth stage: there isn't one yet!\n")); break; } } } /******************************************************************* unbecome the master browser. initates removal of necessary netbios names, and tells the world that we are no longer a master browser. ******************************************************************/ void become_nonmaster(struct subnet_record *d, struct work_record *work, int remove_type) { int new_server_type = work->ServerType; DEBUG(2,("Becoming non-master for %s\n",work->work_group)); /* can only remove master or domain types with this function */ remove_type &= SV_TYPE_MASTER_BROWSER|SV_TYPE_DOMAIN_MASTER; /* unbecome a master browser; unbecome a domain master, too :-( */ if (remove_type & SV_TYPE_MASTER_BROWSER) remove_type |= SV_TYPE_DOMAIN_MASTER; new_server_type &= ~remove_type; if (!(new_server_type & (SV_TYPE_MASTER_BROWSER|SV_TYPE_DOMAIN_MASTER))) { /* no longer a master browser of any sort */ work->ServerType |= SV_TYPE_POTENTIAL_BROWSER; work->ElectionCriterion &= ~0x4; work->state = MST_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,MSBROWSE ,0x01); } work->ServerType = new_server_type; if (!(work->ServerType & SV_TYPE_DOMAIN_MASTER)) { if (work->state == MST_DOMAIN) work->state = MST_BROWSER; remove_name_entry(d,work->work_group,0x1b); } if (!(work->ServerType & SV_TYPE_MASTER_BROWSER)) { if (work->state >= MST_BROWSER) work->state = MST_NONE; remove_name_entry(d,work->work_group,0x1d); } } /******************************************************************* 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 = subnetlist; d; d = d->next) { 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->state = MST_NONE; become_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,ipgrp)) { 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->state = MST_NONE; } } 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)) become_nonmaster(d, work, SV_TYPE_MASTER_BROWSER| SV_TYPE_DOMAIN_MASTER); } } } } /**************************************************************************** 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 = subnetlist; d; d = d->next) { 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; }