From 3605da055737e2cc0fbfffe7772721943a5be8bd Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Tue, 16 Dec 1997 09:20:34 +0000 Subject: [PATCH] Added Lanman announce patch from Jacco de Leeuw . Also added code to stop old Samba servers that announce the workgroup name as master browser name when they are a local master browser. Jeremy. --- source/include/nameserv.h | 1 + source/include/proto.h | 7 ++ source/nmbd/nmbd.c | 10 ++ source/nmbd/nmbd_become_lmb.c | 29 +++++- source/nmbd/nmbd_incomingdgrams.c | 151 ++++++++++++++++++++++++++++-- source/nmbd/nmbd_packets.c | 56 +++++++++++ source/nmbd/nmbd_processlogon.c | 2 +- source/nmbd/nmbd_sendannounce.c | 106 +++++++++++++++++++++ source/param/loadparm.c | 10 ++ 9 files changed, 359 insertions(+), 13 deletions(-) diff --git a/source/include/nameserv.h b/source/include/nameserv.h index 4b7216fef6f..98a6cb330ac 100644 --- a/source/include/nameserv.h +++ b/source/include/nameserv.h @@ -122,6 +122,7 @@ enum netbios_reply_type_code { NMB_QUERY, NMB_STATUS, NMB_REG, NMB_REG_REFRESH, #define BROWSE_MAILSLOT "\\MAILSLOT\\BROWSE" #define NET_LOGON_MAILSLOT "\\MAILSLOT\\NET\\NETLOGON" #define NT_LOGON_MAILSLOT "\\MAILSLOT\\NET\\NTLOGON" +#define LANMAN_MAILSLOT "\\MAILSLOT\\LANMAN" /* Samba definitions for find_name_on_subnet(). */ #define FIND_ANY_NAME 0 diff --git a/source/include/proto.h b/source/include/proto.h index b249c9cb203..a3fd1d1f0f2 100644 --- a/source/include/proto.h +++ b/source/include/proto.h @@ -298,6 +298,8 @@ int lp_lpqcachetime(void); int lp_syslog(void); int lp_client_code_page(void); int lp_announce_as(void); +int lp_lm_announce(void); +int lp_lm_interval(void); char *lp_preexec(int ); char *lp_postexec(int ); char *lp_rootpreexec(int ); @@ -485,6 +487,7 @@ void unbecome_local_master_fail(struct subnet_record *subrec, struct response_re void release_1d_name( struct subnet_record *subrec, char *workgroup_name); void unbecome_local_master_browser(struct subnet_record *subrec, struct work_record *work); void become_local_master_browser(struct subnet_record *subrec, struct work_record *work); +void set_workgroup_local_master_browser_name( struct work_record *work, char *newname); /*The following definitions come from nmbd_browserdb.c */ @@ -517,11 +520,13 @@ void process_workgroup_announce(struct subnet_record *subrec, struct packet_stru void process_local_master_announce(struct subnet_record *subrec, struct packet_struct *p, char *buf); void process_master_browser_announce(struct subnet_record *subrec, struct packet_struct *p,char *buf); +void process_lm_host_announce(struct subnet_record *subrec, struct packet_struct *p, char *buf); void process_get_backup_list_request(struct subnet_record *subrec, struct packet_struct *p,char *buf); void process_reset_browser(struct subnet_record *subrec, struct packet_struct *p,char *buf); void process_announce_request(struct subnet_record *subrec, struct packet_struct *p, char *buf); +void process_lm_announce_request(struct subnet_record *subrec, struct packet_struct *p, char *buf); /*The following definitions come from nmbd_incomingrequests.c */ @@ -670,6 +675,7 @@ void reply_netbios_packet(struct packet_struct *orig_packet, int ttl, char *data,int len); void queue_packet(struct packet_struct *packet); void process_browse_packet(struct packet_struct *p, char *buf,int len); +void process_lanman_packet(struct packet_struct *p, char *buf,int len); BOOL validate_nmb_response_packet( struct nmb_packet *nmb ); BOOL validate_nmb_packet( struct nmb_packet *nmb ); void run_packet_queue(); @@ -706,6 +712,7 @@ struct response_record *find_response_record(struct subnet_record **ppsubrec, void send_browser_reset(int reset_type, char *to_name, int to_type, struct in_addr to_ip); void broadcast_announce_request(struct subnet_record *subrec, struct work_record *work); void announce_my_server_names(time_t t); +void announce_my_lm_server_names(time_t t); void reset_announce_timer(); void announce_myself_to_domain_master_browser(time_t t); void announce_my_servers_removed(void); diff --git a/source/nmbd/nmbd.c b/source/nmbd/nmbd.c index 11cd50cd76f..86f01d8e795 100644 --- a/source/nmbd/nmbd.c +++ b/source/nmbd/nmbd.c @@ -47,6 +47,9 @@ extern char **my_netbios_names; /* are we running as a daemon ? */ static BOOL is_daemon = False; +/* have we found LanMan clients yet? */ +BOOL found_lm_clients = False; + /* what server type are we currently */ time_t StartupTime = 0; @@ -288,6 +291,13 @@ static void process(void) */ announce_my_server_names(t); + /* + * Send out any LanMan broadcast announcements + * of our server names. + * (nmbd_sendannounce.c) + */ + announce_my_lm_server_names(t); + /* * If we are a local master browser, periodically * announce ourselves to the domain master browser. diff --git a/source/nmbd/nmbd_become_lmb.c b/source/nmbd/nmbd_become_lmb.c index 7f54471a241..6496a2f9e58 100644 --- a/source/nmbd/nmbd_become_lmb.c +++ b/source/nmbd/nmbd_become_lmb.c @@ -115,7 +115,7 @@ in workgroup %s on subnet %s\n", /* Forget who the local master browser was for this workgroup. */ - *work->local_master_browser_name = '\0'; + set_workgroup_local_master_browser_name( work, ""); /* * Ensure the IP address of this subnet is not registered as one @@ -333,8 +333,7 @@ on subnet %s\n", work->work_group, subrec->subnet_name)); subrec->work_changed = True; /* Add this name to the workgroup as local master browser. */ - StrnCpy(work->local_master_browser_name, myname, - sizeof(work->local_master_browser_name)-1); + set_workgroup_local_master_browser_name( work, myname); /* Count the number of servers we have on our list. If it's less than 10 (just a heuristic) request the servers @@ -532,3 +531,27 @@ in workgroup %s on subnet %s\n", become_local_master_fail1, userdata); } + +/*************************************************************** + Utility function to set the local master browser name. Does + some sanity checking as old versions of Samba seem to sometimes + say that the master browser name for a workgroup is the same + as the workgroup name. +****************************************************************/ + +void set_workgroup_local_master_browser_name( struct work_record *work, char *newname) +{ + DEBUG(5,("set_workgroup_local_master_browser_name: setting local master name to '%s' \ +for workgroup %s.\n", newname, work->work_group )); + + if(strequal( work->work_group, newname)) + { + DEBUG(5, ("set_workgroup_local_master_browser_name: Refusing to set \ +local_master_browser_name for workgroup %s to workgroup name.\n", + work->work_group )); + return; + } + + StrnCpy(work->local_master_browser_name, newname, + sizeof(work->local_master_browser_name)-1); +} diff --git a/source/nmbd/nmbd_incomingdgrams.c b/source/nmbd/nmbd_incomingdgrams.c index d43b1369e64..452516b64ee 100644 --- a/source/nmbd/nmbd_incomingdgrams.c +++ b/source/nmbd/nmbd_incomingdgrams.c @@ -28,6 +28,7 @@ extern int DEBUGLEVEL; extern pstring myname; extern fstring myworkgroup; +extern BOOL found_lm_clients; #if 0 @@ -223,9 +224,7 @@ void process_workgroup_announce(struct subnet_record *subrec, struct packet_stru if(*work->local_master_browser_name == '\0') { /* Set the master browser name. */ - StrnCpy(work->local_master_browser_name, master_name, - sizeof(work->local_master_browser_name)-1); - + set_workgroup_local_master_browser_name( work, master_name ); } subrec->work_changed = True; @@ -324,9 +323,7 @@ a local master browser for workgroup %s and we think we are master. Forcing elec StrnCpy(servrec->serv.comment,comment,sizeof(servrec->serv.comment)-1); } - /* Set the master browser name. */ - StrnCpy(work->local_master_browser_name, server_name, - sizeof(work->local_master_browser_name)-1); + set_workgroup_local_master_browser_name( work, server_name ); subrec->work_changed = True; } @@ -384,6 +381,105 @@ master - ignoring master announce.\n")); update_browser_death_time(browrec); } +/******************************************************************* + Process an incoming LanMan host announcement packet. +*******************************************************************/ + +void process_lm_host_announce(struct subnet_record *subrec, struct packet_struct *p, char *buf) +{ + struct dgram_packet *dgram = &p->packet.dgram; + uint32 servertype = IVAL(buf,1); + int osmajor=CVAL(buf,5); /* major version of node software */ + int osminor=CVAL(buf,6); /* minor version of node software */ + int ttl = SVAL(buf,7); + char *announce_name = buf+9; + struct work_record *work; + struct server_record *servrec; + char *work_name; + char *source_name = dgram->source_name.name; + pstring comment; + char *s = buf+9; + + s = skip_string(s,1); + StrnCpy(comment, s, 43); + + DEBUG(3,("process_lm_host_announce: LM Announcement from %s<%02x> IP %s to \ +%s for server %s.\n", source_name, source_name[15], inet_ntoa(p->ip), + namestr(&dgram->dest_name),announce_name)); + + DEBUG(5,("process_lm_host_announce: os=(%d,%d) ttl=%d server type=%08x comment=%s\n", + osmajor, osminor, ttl, servertype,comment)); + + if ((osmajor < 36) || (osmajor > 38) || (osminor !=0)) + { + DEBUG(5,("process_lm_host_announce: LM Announcement packet does not " \ + "originate from OS/2 Warp client. Ignoring packet.\n")); + /* Could have been from a Windows machine (with its LM Announce enabled), + or a Samba server. Then don't disrupt the current browse list. */ + return; + } + + /* Filter servertype to remove impossible bits. */ + servertype &= ~(SV_TYPE_LOCAL_LIST_ONLY|SV_TYPE_DOMAIN_ENUM); + + /* A LanMan host announcement must be sent to the name WORKGROUP<00>. */ + if(dgram->dest_name.name_type != 0x00) + { + DEBUG(2,("process_lm_host_announce: incorrect name type for destination from IP %s \ +(was %02x) should be 0x00. Allowing packet anyway.\n", + inet_ntoa(p->ip), dgram->dest_name.name_type)); + /* Change it so it was. */ + dgram->dest_name.name_type = 0x00; + } + + /* For a LanMan host announce the workgroup name is the destination name. */ + work_name = dgram->dest_name.name; + + /* + * Syntax servers version 5.1 send HostAnnounce packets to + * *THE WRONG NAME*. They send to LOCAL_MASTER_BROWSER_NAME<00> + * instead of WORKGROUP<1d> name. So to fix this we check if + * the workgroup name is our own name, and if so change it + * to be our primary workgroup name. This code is probably + * not needed in the LanMan announce code, but it won't hurt. + */ + + if(strequal(work_name, myname)) + work_name = myworkgroup; + + /* + * We are being very agressive here in adding a workgroup + * name on the basis of a host announcing itself as being + * in that workgroup. Maybe we should wait for the workgroup + * announce instead ? JRA. + */ + + if ((work = find_workgroup_on_subnet(subrec, work_name))==NULL) + { + /* We have no record of this workgroup. Add it. */ + if((work = create_workgroup_on_subnet(subrec, work_name, ttl))==NULL) + return; + } + + if((servrec = find_server_in_workgroup( work, announce_name))==NULL) + { + /* If this server is not already in the workgroup, add it. */ + create_server_on_workgroup(work, announce_name, + servertype|SV_TYPE_LOCAL_LIST_ONLY, + ttl, comment); + } + else + { + /* Update the record. */ + servrec->serv.type = servertype|SV_TYPE_LOCAL_LIST_ONLY; + update_server_ttl( servrec, ttl); + StrnCpy(servrec->serv.comment,comment,sizeof(servrec->serv.comment)-1); + } + + subrec->work_changed = True; + found_lm_clients = True; +} + /**************************************************************************** Send a backup list response. *****************************************************************************/ @@ -600,12 +696,12 @@ request from %s IP %s state=0x%X\n", } /******************************************************************* - Process a announcement request packet. + Process an announcement request packet. We don't respond immediately, we just check it's a request for - out workgroup and then set the flag telling the announce code + our workgroup and then set the flag telling the announce code in nmbd_sendannounce.c:announce_my_server_names that an announcement is needed soon. - ******************************************************************/ +******************************************************************/ void process_announce_request(struct subnet_record *subrec, struct packet_struct *p, char *buf) { @@ -634,3 +730,40 @@ void process_announce_request(struct subnet_record *subrec, struct packet_struct work->needannounce = True; } + +/******************************************************************* + Process a LanMan announcement request packet. + We don't respond immediately, we just check it's a request for + our workgroup and then set the flag telling that we have found + a LanMan client (DOS or OS/2) and that we will have to start + sending LanMan announcements (unless specifically disabled + through the "lm_announce" parameter in smb.conf) +******************************************************************/ + +void process_lm_announce_request(struct subnet_record *subrec, struct packet_struct *p, char *buf) +{ + struct dgram_packet *dgram = &p->packet.dgram; + struct work_record *work; + char *workgroup_name = dgram->dest_name.name; + + DEBUG(3,("process_lm_announce_request: Announce request from %s IP %s to %s.\n", + namestr(&dgram->source_name), inet_ntoa(p->ip), + namestr(&dgram->dest_name))); + + /* We only send announcement requests on our workgroup. */ + if(strequal(workgroup_name, myworkgroup) == False) + { + DEBUG(7,("process_lm_announce_request: Ignoring announce request for workgroup %s.\n", + workgroup_name)); + return; + } + + if((work = find_workgroup_on_subnet(subrec, workgroup_name)) == NULL) + { + DEBUG(0,("process_announce_request: Unable to find workgroup %s on subnet !\n", + workgroup_name)); + return; + } + + found_lm_clients = True; +} diff --git a/source/nmbd/nmbd_packets.c b/source/nmbd/nmbd_packets.c index 43249cc0a33..4fb05439673 100644 --- a/source/nmbd/nmbd_packets.c +++ b/source/nmbd/nmbd_packets.c @@ -1023,6 +1023,56 @@ command code %d from %s IP %s to %s\n", } } +/**************************************************************************** + Dispatch a LanMan browse frame from port 138 to the correct processing function. +****************************************************************************/ + +void process_lanman_packet(struct packet_struct *p, char *buf,int len) +{ + struct dgram_packet *dgram = &p->packet.dgram; + int command = SVAL(buf,0); + struct subnet_record *subrec = find_subnet_for_dgram_browse_packet(p); + + /* Drop the packet if it's a different NetBIOS scope, or + the source is from one of our names. */ + + if (!strequal(dgram->dest_name.scope,scope )) + { + DEBUG(7,("process_lanman_packet: Discarding datagram from IP %s. Scope (%s) \ +mismatch with our scope (%s).\n", inet_ntoa(p->ip), dgram->dest_name.scope, scope)); + return; + } + + if (is_myname(dgram->source_name.name)) + { + DEBUG(0,("process_lanman_packet: Discarding datagram from IP %s. Source name \ +%s is one of our names !\n", inet_ntoa(p->ip), namestr(&dgram->source_name))); + return; + } + + switch (command) + { + case ANN_HostAnnouncement: + { + debug_browse_data(buf, len); + process_lm_host_announce(subrec, p, buf+1); + break; + } + case ANN_AnnouncementRequest: + { + process_lm_announce_request(subrec, p, buf+1); + break; + } + default: + { + DEBUG(0,("process_lanman_packet: On subnet %s ignoring browse packet \ +command code %d from %s IP %s to %s\n", + subrec->subnet_name, command, namestr(&dgram->source_name), + inet_ntoa(p->ip), namestr(&dgram->dest_name))); + } + } +} + /**************************************************************************** Determine if a packet is for us on port 138. Note that to have any chance of being efficient we need to drop as many packets as possible at this @@ -1100,6 +1150,12 @@ static void process_dgram(struct packet_struct *p) return; } + /* Datagram packet received for the LAN Manager mailslot */ + if (strequal(smb_buf(buf),LANMAN_MAILSLOT)) { + process_lanman_packet(p,buf2,len); + return; + } + /* Datagram packet received for the domain logon mailslot */ if (strequal(smb_buf(buf),NET_LOGON_MAILSLOT)) { diff --git a/source/nmbd/nmbd_processlogon.c b/source/nmbd/nmbd_processlogon.c index ae917564fe2..cd2fbfd0a56 100644 --- a/source/nmbd/nmbd_processlogon.c +++ b/source/nmbd/nmbd_processlogon.c @@ -63,7 +63,7 @@ void process_logon_packet(struct packet_struct *p,char *buf,int len, if (!lp_domain_logons()) { - DEBUG(3,("process_logon_packet: Logon packet received from IP %S and domain \ + DEBUG(3,("process_logon_packet: Logon packet received from IP %s and domain \ logons are not enabled.\n", inet_ntoa(p->ip) )); return; } diff --git a/source/nmbd/nmbd_sendannounce.c b/source/nmbd/nmbd_sendannounce.c index e4b288aea59..aac3dad3662 100644 --- a/source/nmbd/nmbd_sendannounce.c +++ b/source/nmbd/nmbd_sendannounce.c @@ -32,6 +32,7 @@ extern pstring myname; extern fstring myworkgroup; extern char **my_netbios_names; extern int updatecount; +extern BOOL found_lm_clients; /**************************************************************************** Send a browser reset packet. @@ -126,6 +127,37 @@ static void send_announcement(struct subnet_record *subrec, int announce_type, from_name, 0x0, to_name, to_type, to_ip, subrec->myip); } +/**************************************************************************** + Broadcast a LanMan announcement. +**************************************************************************/ + +static void send_lm_announcement(struct subnet_record *subrec, int announce_type, + char *from_name, 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=outbuf; + + bzero(outbuf,sizeof(outbuf)); + + SSVAL(p,0,announce_type); + SIVAL(p,2,server_type & ~SV_TYPE_LOCAL_LIST_ONLY); + CVAL(p,6) = lp_major_announce_version(); /* Major version. */ + CVAL(p,7) = lp_minor_announce_version(); /* Minor version. */ + SSVAL(p,8,announce_interval); /* In seconds - according to spec. */ + + p += 10; + StrnCpy(p,server_name,15); + strupper(p); + p = skip_string(p,1); + pstrcpy(p,server_comment); + p = skip_string(p,1); + + send_mailslot(False,LANMAN_MAILSLOT, outbuf, PTR_DIFF(p,outbuf), + from_name, 0x0, to_name, to_type, to_ip, subrec->myip); +} + /**************************************************************************** We are a local master browser. Announce this to WORKGROUP<1e>. ****************************************************************************/ @@ -191,6 +223,29 @@ static void send_host_announcement(struct subnet_record *subrec, struct work_rec servrec->serv.comment); } +/**************************************************************************** + Announce the given LanMan host +****************************************************************************/ + +static void send_lm_host_announcement(struct subnet_record *subrec, struct work_record *work, + struct server_record *servrec, int lm_interval) +{ + /* Ensure we don't have the prohibited bits set. */ + uint32 type = servrec->serv.type & ~SV_TYPE_LOCAL_LIST_ONLY; + + DEBUG(3,("send_lm_host_announcement: type %x for host %s on subnet %s for workgroup %s, ttl: %d\n", + type, servrec->serv.name, subrec->subnet_name, work->work_group, lm_interval)); + + send_lm_announcement(subrec, ANN_HostAnnouncement, + servrec->serv.name, /* From nbt name. */ + work->work_group, 0x00, /* To nbt name. */ + subrec->bcast_ip, /* To ip. */ + lm_interval, /* Time until next announce. */ + servrec->serv.name, /* Name to announce. */ + type, /* Type field. */ + servrec->serv.comment); +} + /**************************************************************************** Announce a server record. ****************************************************************************/ @@ -258,6 +313,56 @@ void announce_my_server_names(time_t t) } /* for subrec */ } +/**************************************************************************** + Go through all my registered names on all broadcast subnets and announce + them as a LanMan server if the timeout requires it. +**************************************************************************/ + +void announce_my_lm_server_names(time_t t) +{ + struct subnet_record *subrec; + static time_t last_lm_announce_time=0; + int announce_interval = lp_lm_interval(); + int lm_announce = lp_lm_announce(); + + if ((announce_interval <= 0) || (lm_announce <= 0)) + { + /* user absolutely does not want LM announcements to be sent. */ + return; + } + + if ((lm_announce >= 2) && (!found_lm_clients)) + { + /* has been set to 2 (Auto) but no LM clients detected (yet). */ + return; + } + + /* Otherwise: must have been set to 1 (Yes), or LM clients *have* + been detected. */ + + for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) + { + struct work_record *work = find_workgroup_on_subnet(subrec, myworkgroup); + + if(work) + { + struct server_record *servrec; + + if (last_lm_announce_time && ((t - last_lm_announce_time) < announce_interval )) + continue; + + last_lm_announce_time = t; + + for (servrec = work->serverlist; servrec; servrec = servrec->next) + { + if (is_myname(servrec->serv.name)) + /* skipping equivalent of announce_server() */ + send_lm_host_announcement(subrec, work, servrec, announce_interval); + } + } /* if work */ + } /* for subrec */ +} + /* Announce timer. Moved into global static so it can be reset when a machine becomes a local master browser. */ static time_t announce_timer_last=0; @@ -342,6 +447,7 @@ void announce_my_servers_removed(void) if(AM_LOCAL_MASTER_BROWSER(work)) send_local_master_announcement(subrec, work, servrec); send_host_announcement(subrec, work, servrec); + send_lm_host_announcement(subrec, work, servrec, 0); } } } diff --git a/source/param/loadparm.c b/source/param/loadparm.c index 76618e9a791..277430ecc44 100644 --- a/source/param/loadparm.c +++ b/source/param/loadparm.c @@ -160,6 +160,8 @@ typedef struct int max_wins_ttl; int min_wins_ttl; int ReadSize; + int lm_announce; + int lm_interval; int shmem_size; int client_code_page; int announce_as; /* This is initialised in init_globals */ @@ -409,6 +411,8 @@ static struct enum_list enum_announce_as[] = {{ANNOUNCE_AS_NT, "NT"}, {ANNOUNCE_ static struct enum_list enum_case[] = {{CASE_LOWER, "lower"}, {CASE_UPPER, "upper"}, {-1, NULL}}; +static struct enum_list enum_lm_announce[] = {{0, "False"}, {1, "True"}, {2, "Auto"}}; + static struct parm_struct { char *label; @@ -507,6 +511,8 @@ static struct parm_struct {"max ttl", P_INTEGER, P_GLOBAL, &Globals.max_ttl, NULL, NULL}, {"max wins ttl", P_INTEGER, P_GLOBAL, &Globals.max_wins_ttl, NULL, NULL}, {"min wins ttl", P_INTEGER, P_GLOBAL, &Globals.min_wins_ttl, NULL, NULL}, + {"lm announce", P_ENUM, P_GLOBAL, &Globals.lm_announce, NULL, enum_lm_announce}, + {"lm interval", P_INTEGER, P_GLOBAL, &Globals.lm_interval, NULL, NULL}, {"dns proxy", P_BOOL, P_GLOBAL, &Globals.bDNSproxy, NULL, NULL}, {"wins support", P_BOOL, P_GLOBAL, &Globals.bWINSsupport, NULL, NULL}, {"wins proxy", P_BOOL, P_GLOBAL, &Globals.bWINSproxy, NULL, NULL}, @@ -699,6 +705,8 @@ static void init_globals(void) Globals.max_wins_ttl = 60*60*24*3; /* 3 days default */ Globals.min_wins_ttl = 60*60*6; /* 6 hours default */ Globals.ReadSize = 16*1024; + Globals.lm_announce = 2; /* = Auto: send only if LM clients found */ + Globals.lm_interval = 60; Globals.shmem_size = SHMEM_SIZE; Globals.announce_as = ANNOUNCE_AS_NT; Globals.bUnixRealname = False; @@ -947,6 +955,8 @@ FN_GLOBAL_INTEGER(lp_lpqcachetime,&Globals.lpqcachetime) FN_GLOBAL_INTEGER(lp_syslog,&Globals.syslog) FN_GLOBAL_INTEGER(lp_client_code_page,&Globals.client_code_page) FN_GLOBAL_INTEGER(lp_announce_as,&Globals.announce_as) +FN_GLOBAL_INTEGER(lp_lm_announce,&Globals.lm_announce) +FN_GLOBAL_INTEGER(lp_lm_interval,&Globals.lm_interval) FN_LOCAL_STRING(lp_preexec,szPreExec) FN_LOCAL_STRING(lp_postexec,szPostExec) -- 2.34.1