1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-28 07:21:54 +03:00
samba-mirror/source3/nameelect.c
Andrew Tridgell 8781e4d787 - changed the default nmbd loop timout to 10 seconds (2 seconds was much
too short)

- got rid of many unnecessary calls to time(NULL) in nmbd. They were
causing it to chew too much CPU time when idle. Now we pass a time
value in from the top level.
(This used to be commit 3cd7303dbc)
1996-10-07 01:56:21 +00:00

636 lines
18 KiB
C

/*
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;
}