/* Unix SMB/Netbios implementation. Version 1.9. NBT netbios routines and daemon - version 2 Copyright (C) Andrew Tridgell 1994-1997 SMB Version handling Copyright (C) John H Terpstra 1995-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. Revision History: 14 jan 96: lkcl@pires.co.uk added multiple workgroup domain master support */ #include "includes.h" #define TEST_CODE extern int DEBUGLEVEL; extern BOOL CanRecurse; extern struct in_addr ipzero; extern pstring myname; extern fstring myworkgroup; extern int ClientDGRAM; extern int ClientNMB; /* this is our domain/workgroup/server database */ extern struct subnet_record *subnetlist; extern int updatecount; extern int workgroup_count; extern struct in_addr wins_ip; extern pstring scope; /**************************************************************************** send a announce request to the local net **************************************************************************/ void announce_request(struct work_record *work, struct in_addr ip) { pstring outbuf; char *p; if (!work) return; work->needannounce = True; DEBUG(2,("sending announce request to %s for workgroup %s\n", inet_ntoa(ip),work->work_group)); bzero(outbuf,sizeof(outbuf)); p = outbuf; CVAL(p,0) = ANN_AnnouncementRequest; p++; CVAL(p,0) = work->token; /* (local) unique workgroup token id */ p++; StrnCpy(p,myname,16); strupper(p); p = skip_string(p,1); /* XXXX note: if we sent the announcement request to 0x1d instead of 0x1e, then we could get the master browser to announce to us instead of the members of the workgroup. wha-hey! */ send_mailslot_reply(False, BROWSE_MAILSLOT,ClientDGRAM, outbuf,PTR_DIFF(p,outbuf), myname,work->work_group,0x20,0x1e,ip,*iface_ip(ip)); } /**************************************************************************** request an announcement **************************************************************************/ void do_announce_request(char *info, char *to_name, int announce_type, int from, int to, struct in_addr dest_ip) { pstring outbuf; char *p; bzero(outbuf,sizeof(outbuf)); p = outbuf; CVAL(p,0) = announce_type; p++; DEBUG(2,("sending announce type %d: info %s to %s - server %s(%x)\n", announce_type, info, inet_ntoa(dest_ip),to_name,to)); StrnCpy(p,info,16); strupper(p); p = skip_string(p,1); send_mailslot_reply(False,BROWSE_MAILSLOT,ClientDGRAM, outbuf,PTR_DIFF(p,outbuf), myname,to_name,from,to,dest_ip,*iface_ip(dest_ip)); } /**************************************************************************** find a server responsible for a workgroup, and sync browse lists control ends up back here via response_name_query. **************************************************************************/ void sync_server(enum state_type state, char *serv_name, char *work_name, int name_type, struct subnet_record *d, struct in_addr ip) { /* with a domain master we can get the whole list (not local only list) */ BOOL local_only = (state != NAME_STATUS_DOM_SRV_CHK); add_browser_entry(serv_name, name_type, work_name, 0, d, ip, local_only); if (state == NAME_STATUS_DOM_SRV_CHK) { /* announce ourselves as a master browser to serv_name */ do_announce_request(myname, serv_name, ANN_MasterAnnouncement, 0x20, 0, ip); } } /**************************************************************************** send a host announcement packet **************************************************************************/ void do_announce_host(int command, char *from_name, int from_type, struct in_addr from_ip, char *to_name , int to_type , struct in_addr to_ip, time_t announce_interval, char *server_name, int server_type, char *server_comment) { pstring outbuf; char *p; bzero(outbuf,sizeof(outbuf)); p = outbuf+1; /* command type */ CVAL(outbuf,0) = command; /* announcement parameters */ CVAL(p,0) = updatecount; SIVAL(p,1,announce_interval*1000); /* ms - despite the spec */ StrnCpy(p+5,server_name,16); strupper(p+5); CVAL(p,21) = lp_major_announce_version(); /* major version */ CVAL(p,22) = lp_minor_announce_version(); /* minor version */ SIVAL(p,23,server_type & ~SV_TYPE_LOCAL_LIST_ONLY); /* browse version: got from NT/AS 4.00 - Value defined in smb.h (JHT)*/ SSVAL(p,27,BROWSER_ELECTION_VERSION); SSVAL(p,29,BROWSER_CONSTANT); /* browse signature */ strcpy(p+31,server_comment); p += 31; p = skip_string(p,1); debug_browse_data(outbuf, PTR_DIFF(p,outbuf)); /* send the announcement */ send_mailslot_reply(False,BROWSE_MAILSLOT,ClientDGRAM,outbuf, PTR_DIFF(p,outbuf), from_name, to_name, from_type, to_type, to_ip, from_ip); } /**************************************************************************** announce all samba's server entries as 'gone'. ****************************************************************************/ void announce_my_servers_removed(void) { struct subnet_record *d; for (d = FIRST_SUBNET; d; d = NEXT_SUBNET_EXCLUDING_WINS(d)) { struct work_record *work; for (work = d->workgrouplist; work; work = work->next) { struct server_record *s; for (s = work->serverlist; s; s = s->next) { if (!strequal(myname,s->serv.name)) continue; announce_server(d, work, s->serv.name, s->serv.comment, 0, 0); } } } } /**************************************************************************** announce a server entry ****************************************************************************/ void announce_server(struct subnet_record *d, struct work_record *work, char *name, char *comment, time_t ttl, int server_type) { /* domain type cannot have anything in it that might confuse a client into thinking that the domain is in fact a server. (SV_TYPE_SERVER_UNIX, for example) */ uint32 domain_type = SV_TYPE_DOMAIN_ENUM|SV_TYPE_NT; BOOL wins_iface = ip_equal(d->bcast_ip, wins_ip); if(wins_iface) { DEBUG(0,("announce_server: error - announcement requested on WINS \ interface for workgroup %s, name %s\n", work->work_group, name)); return; } if (AM_MASTER(work)) { DEBUG(3,("sending local master announce to %s for %s(1e)\n", inet_ntoa(d->bcast_ip),work->work_group)); do_announce_host(ANN_LocalMasterAnnouncement, name , 0x00, d->myip, work->work_group, 0x1e, d->bcast_ip, ttl, name, server_type, comment); DEBUG(3,("sending domain announce to %s for %s\n", inet_ntoa(d->bcast_ip),work->work_group)); /* XXXX should we do a domain-announce-kill? */ if (server_type != 0) { do_announce_host(ANN_DomainAnnouncement, name , 0x00, d->myip, MSBROWSE, 0x01, d->bcast_ip, ttl, work->work_group, server_type ? domain_type : 0, name); } } else { DEBUG(3,("sending host announce to %s for %s(1d)\n", inet_ntoa(d->bcast_ip),work->work_group)); do_announce_host(ANN_HostAnnouncement, name , 0x00, d->myip, work->work_group, 0x1d, d->bcast_ip, ttl, name, server_type, comment); } } /**************************************************************************** construct a host announcement unicast **************************************************************************/ void announce_host(time_t t) { struct subnet_record *d; pstring comment; char *my_name; StrnCpy(comment, lp_serverstring(), 43); my_name = *myname ? myname : "NoName"; for (d = FIRST_SUBNET; d; d = NEXT_SUBNET_EXCLUDING_WINS(d)) { struct work_record *work; for (work = d->workgrouplist; work; work = work->next) { uint32 stype = work->ServerType; struct server_record *s; BOOL announce = False; /* must work on the code that does announcements at up to 30 seconds later if a master browser sends us a request announce. */ if (work->needannounce) { /* drop back to a max 3 minute announce - this is to prevent a single lost packet from stuffing things up for too long */ work->announce_interval = MIN(work->announce_interval, CHECK_TIME_MIN_HOST_ANNCE*60); work->lastannounce_time = t - (work->announce_interval+1); } /* announce every minute at first then progress to every 12 mins */ if (work->lastannounce_time && (t - work->lastannounce_time) < work->announce_interval) continue; if (work->announce_interval < CHECK_TIME_MAX_HOST_ANNCE * 60) work->announce_interval += 60; work->lastannounce_time = t; for (s = work->serverlist; s; s = s->next) { if (strequal(myname, s->serv.name)) { announce = True; break; } } if (announce) { announce_server(d,work,my_name,comment, work->announce_interval,stype); } if (work->needannounce) { work->needannounce = False; break; /* sorry: can't do too many announces. do some more later */ } } } } /* Announce timer. Moved into global static so it can be reset when a machine becomes a master browser. */ static time_t announce_timer_last=0; /**************************************************************************** Reset the announce_timer so that a master browser announce will be done immediately. ****************************************************************************/ void reset_announce_timer() { announce_timer_last = time(NULL) - (CHECK_TIME_MST_ANNOUNCE * 60); } /**************************************************************************** announce myself as a master to all other domain master browsers. this actually gets done in search_and_sync_workgroups() via the NAME_QUERY_DOM_SRV_CHK command, if there is a response from the name query initiated here. see response_name_query() **************************************************************************/ void announce_master(time_t t) { struct subnet_record *d; struct work_record *work; BOOL am_master = False; /* are we a master of some sort? :-) */ if (!announce_timer_last) announce_timer_last = t; if (t-announce_timer_last < CHECK_TIME_MST_ANNOUNCE * 60) { DEBUG(10,("announce_master: t (%d) - last(%d) < %d\n", t, announce_timer_last, CHECK_TIME_MST_ANNOUNCE * 60 )); return; } if(wins_subnet == NULL) { DEBUG(10,("announce_master: no wins subnet, ignoring.\n")); return; } announce_timer_last = 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 (AM_MASTER(work)) { am_master = True; DEBUG(4,( "announce_master: am_master = %d for \ workgroup %s\n", am_master, work->work_group)); } } } if (!am_master) return; /* only proceed if we are a master browser */ /* Note that we don't do this if we are domain master browser and that we *only* do this on the WINS subnet. */ /* Try and find our workgroup on the WINS subnet */ work = find_workgroupstruct(wins_subnet, myworkgroup, False); if (work) { char *name; int type; { /* assume that the domain master browser we want to sync with is our own domain. */ name = work->work_group; type = 0x1b; } /* check the existence of a dmb for this workgroup, and if one exists at the specified ip, sync with it and announce ourselves as a master browser to it */ if (!lp_wins_support() && *lp_wins_server() ) { DEBUG(4, ("Local Announce: find %s<%02x> from WINS server %s\n", name, type, lp_wins_server())); queue_netbios_pkt_wins(ClientNMB, NMB_QUERY,NAME_QUERY_DOM_SRV_CHK, name, type, 0,0,0, work->work_group,NULL, False, True, ipzero, ipzero); } else if(lp_wins_support()) { /* We are the WINS server - query ourselves for the dmb name. */ struct nmb_name netb_name; struct subnet_record *d = 0; struct name_record *nr = 0; make_nmb_name(&netb_name, name, type, scope); if ((nr = find_name_search(&d, &netb_name, FIND_WINS, ipzero)) == 0) { DEBUG(0, ("announce_master: unable to find domain master browser for workgroup %s \ in our own WINS database.\n", work->work_group)); return; } /* Check that this isn't one of our addresses (ie. we are not domain master ourselves) */ if(ismyip(nr->ip_flgs[0].ip) || ip_equal(nr->ip_flgs[0].ip, ipzero)) { DEBUG(4, ("announce_master: domain master ip found (%s) for workgroup %s \ is one of our interfaces.\n", work->work_group, inet_ntoa(nr->ip_flgs[0].ip) )); return; } /* Issue a NAME_STATUS_DOM_SRV_CHK immediately - short circuit the NAME_QUERY_DOM_SRV_CHK which is done only if we are talking to a remote WINS server. */ DEBUG(4, ("announce_master: doing name status for %s<%02x> to domain master ip %s \ for workgroup %s\n", name, type, inet_ntoa(nr->ip_flgs[0].ip), work->work_group )); queue_netbios_packet(wins_subnet, ClientNMB, NMB_QUERY,NAME_STATUS_DOM_SRV_CHK, name, type, 0,0,0, work->work_group,NULL, False, False, nr->ip_flgs[0].ip, nr->ip_flgs[0].ip); } } } /**************************************************************************** do all the "remote" announcements. These are used to put ourselves on a remote browse list. They are done blind, no checking is done to see if there is actually a browse master at the other end. **************************************************************************/ void announce_remote(time_t t) { char *s,*ptr; static time_t last_time = 0; pstring s2; struct in_addr addr; char *comment,*workgroup; int stype = lp_default_server_announce(); if (last_time && t < last_time + REMOTE_ANNOUNCE_INTERVAL) return; last_time = t; s = lp_remote_announce(); if (!*s) return; comment = lp_serverstring(); workgroup = myworkgroup; for (ptr=s; next_token(&ptr,s2,NULL); ) { /* the entries are of the form a.b.c.d/WORKGROUP with WORKGROUP being optional */ char *wgroup; wgroup = strchr(s2,'/'); if (wgroup) *wgroup++ = 0; if (!wgroup || !*wgroup) wgroup = workgroup; addr = *interpret_addr2(s2); do_announce_host(ANN_HostAnnouncement,myname,0x20,*iface_ip(addr), wgroup,0x1e,addr, REMOTE_ANNOUNCE_INTERVAL, myname,stype,comment); } }