diff options
Diffstat (limited to 'source/nameelect.c')
-rw-r--r-- | source/nameelect.c | 634 |
1 files changed, 634 insertions, 0 deletions
diff --git a/source/nameelect.c b/source/nameelect.c new file mode 100644 index 00000000000..38b4d5d80e7 --- /dev/null +++ b/source/nameelect.c @@ -0,0 +1,634 @@ +/* + 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; + +/* machine comment for host announcements */ +extern pstring ServerComment; + +/* 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(void) +{ + static time_t lastrun=0; + time_t t = time(NULL); + 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; + + 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) +{ + uint32 domain_type = SV_TYPE_DOMAIN_ENUM|SV_TYPE_SERVER_UNIX|0x00400000; + + 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,ServerComment,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,ServerComment,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,ServerComment,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 |= SV_TYPE_DOMAIN_MASTER; + + if (lp_domain_logons()) + { + update_type |= SV_TYPE_DOMAIN_CTRL|SV_TYPE_SERVER_UNIX; + } + + work->ServerType |= update_type; + add_server_entry(d,work,myname,work->ServerType,0,ServerComment,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_DOMAIN_MASTER)) + { + if (work->state >= MST_BROWSER) + work->state = MST_NONE; + remove_name_entry(d,work->work_group,0x1d); + } +} + + +/******************************************************************* + run the election + ******************************************************************/ +void run_elections(void) +{ + time_t t = time(NULL); + 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) +{ + time_t t = time(NULL); + uint32 mycriterion; + if (version > ELECTION_VERSION) return(False); + if (version < ELECTION_VERSION) return(True); + + mycriterion = work->ElectionCriterion; + + if (criterion > mycriterion) return(False); + if (criterion < mycriterion) return(True); + + if (timeup > (t - StartupTime)) return(False); + if (timeup < (t - StartupTime)) 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; + + name[15] = 0; + + DEBUG(3,("Election request from %s vers=%d criterion=%08x timeup=%d\n", + name,version,criterion,timeup)); + + if (same_context(dgram)) return; + + for (work = d->workgrouplist; work; work = work->next) + { + if (listening_name(work, &dgram->dest_name) && + strequal(work->work_group, lp_workgroup())) + { + 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) + { + work->RunningElection = False; + DEBUG(3,(">>> Lost election on %s %s <<<\n", + work->work_group,inet_ntoa(d->bcast_ip))); + + /* if we are the master then remove our masterly names */ + 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; +} + |