r3708: BUG 1838: patch from Gavrie Philipson <gavrie@disksites.com> to remove stale...
[ira/wip.git] / source / lib / wins_srv.c
index e6c0c516083685b18b8d4af687cdd66e5cf6060b..4a54762fde74578d2432d44b6a77c7b87eb13052 100644 (file)
@@ -1,9 +1,9 @@
 /*
-   Unix SMB/Netbios implementation.
-   Version 2.
-   Samba utility functions
-   Copyright (C) Andrew Tridgell 1992-1998
+   Unix SMB/CIFS implementation.
+   Samba wins server helper functions
+   Copyright (C) Andrew Tridgell 1992-2002
    Copyright (C) Christopher R. Hertel 2000
+   Copyright (C) Tim Potter 2003
 
    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
 
 #include "includes.h"
 
-/* -------------------------------------------------------------------------- **
- * Discussion...
- *
- * This module implements WINS failover.
- *
- * Microsoft's WINS servers provide a feature called WINS replication,
- * which synchronises the WINS name databases between two or more servers. 
- * This means that the two servers can be used interchangably (more or
- * less). WINS replication is particularly useful if you are trying to
- * synchronise the WINS namespace between servers in remote locations, or
- * if your WINS servers tend to crash a lot. 
- *
- * WINS failover allows the client to 'switch' to a different WINS server
- * if the current WINS server mysteriously disappears.  On Windows
- * systems, this is typically represented as 'primary' and 'secondary'
- * WINS servers. 
- *
- * Failover only works if the WINS servers are synced.  If they are not,
- * then
- *   a) if the primary WINS server never fails the client will never 'see'
- *      the secondary (or tertiary or...) WINS server name space.
- *   b) if the primary *does* fail, the client will be entering an
- *      unfamiliar namespace.  The client itself will not be registered in
- *      that namespace and any names which match names in the previous
- *      space will likely resolve to different host IP addresses.
- *
- * One key thing to remember regarding WINS failover is that Samba does
- * not (yet) implement WINS replication.  For those interested, sniff port
- * 42 (TCP? UDP? ...dunno off hand) and see what two MS WINS servers do. 
- *
- * At this stage, only failover is implemented.  The next thing is to add
- * support for multi-WINS server registration and query (multi-membership).
- *
- * Multi-membership is a little wierd.  The idea is that the client can
- * register itself with multiple non-replicated WINS servers, and query
- * all of those servers (in a prescribed sequence) to resolve a name. 
- *
- * The implications of multi-membership are not quite clear.  Worth
- * trying, I suppose.  Changes will be needed in the name query and
- * registration code to accomodate this feature.  Also, there will need to
- * be some sort of syntax extension for the 'wins server' parameter in
- * smb.conf.  I'm thinking that a colon could be used as a separator. 
- *
- * Of course, for each WINS namespace there might be multiple, synced WINS
- * servers.  The change to this module would likely be the addition of a
- * linked list of linked lists.
- *
- * crh@samba.org
- */
-
-/* -------------------------------------------------------------------------- **
- * Defines... 
- *
- *   NECROMANCYCLE - The dead server retry period, in seconds.  When a WINS
- *                   server is declared dead, wait this many seconds before
- *                   attempting to communicate with it.
- */
-
-#define NECROMANCYCLE 600   /* 600 seconds == 10 minutes. */
-
-/* -------------------------------------------------------------------------- **
- * Typedefs...
- */
-
-typedef struct
-  {
-  ubi_slNode     node;      /* Linked list node structure.                  */
-  time_t         mourning;  /* If > current time then server is dead, Jim.  */
-  char          *server;    /* DNS name or IP of NBNS server to query.      */
-  struct in_addr ip_addr;   /* Cache translated IP.                         */
-  } list_entry;
-
-/* -------------------------------------------------------------------------- **
- * Private, static variables.
- */
-
-static ubi_slNewList( wins_srv_list );
-
-/* -------------------------------------------------------------------------- **
- * Functions...
- */
-
-
-BOOL wins_srv_load_list( char *src )
-  /* ------------------------------------------------------------------------ **
-   * Create or recreate the linked list of failover WINS servers.
-   *
-   *  Input:  src - String of DNS names and/or IP addresses delimited by the
-   *                characters listed in LIST_SEP (see include/local.h).
-   *
-   *  Output: True if at least one name or IP could be parsed out of the
-   *          list, else False.
-   *
-   *  Notes:  There is no syntax checking done on the names or IPs.  We do
-   *          check to see if the field is an IP, in which case we copy it
-   *          to the ip_addr field of the entry.  Don't bother to to a host
-   *          name lookup on all names now.  They're done as needed in
-   *          wins_srv_ip().
-   * ------------------------------------------------------------------------ **
-   */
-  {
-  list_entry   *entry;
-  char         *p = src;
-  pstring       wins_id_bufr;
-  unsigned long count;
-
-  /* Empty the list. */
-  while( NULL != (entry =(list_entry *)ubi_slRemHead( wins_srv_list )) )
-    {
-    if( entry->server )
-      free( entry->server );
-    free( entry );
-    }
-  (void)ubi_slInitList( wins_srv_list );  /* shouldn't be needed */
-
-  /* Parse out the DNS names or IP addresses of the WINS servers. */
-  DEBUG( 4, ("wins_srv_load_list(): Building WINS server list:\n") );
-  while( next_token( &p, wins_id_bufr, LIST_SEP, sizeof( wins_id_bufr ) ) )
-    {
-    entry = (list_entry *)malloc( sizeof( list_entry ) );
-    if( NULL == entry )
-      {
-      DEBUG( 0, ("wins_srv_load_list(): malloc(list_entry) failed.\n") );
-      }
-    else
-      {
-      entry->mourning = 0;
-      /* Create a copy of the server name and store it in the list. */
-      if( NULL == (entry->server = strdup( wins_id_bufr )) )
-        {
-        free( entry );
-        DEBUG( 0,
-          ("wins_srv_load_list(): strdup(\"%s\") failed.\n", wins_id_bufr) );
-        }
-      else
-        {
-        /* Add server to list.
-         * If the server name was actually an IP address we will store that
-         * too.  It it was a DNS name, we will wait until we need to use
-         * the WINS server before doing the DNS lookup.  Since this may be
-         * a list, and since we will reload the list whenever smb.conf is
-         * reloaded, there's no point in trying to look up names until we
-         * need them.  ...of course, once we do the lookup we will cache
-         * the result.  See wins_srv_ip().
-         */
-        if( is_ipaddress( wins_id_bufr ) )
-          entry->ip_addr = *interpret_addr2( wins_id_bufr );
-        else
-          entry->ip_addr = *interpret_addr2( "0.0.0.0" );
-        (void)ubi_slAddTail( wins_srv_list, entry );
-        DEBUGADD( 4, ("%s,\n", wins_id_bufr) );
-        }
-      }
-    }
-
-  count = ubi_slCount( wins_srv_list );
-  DEBUGADD( 4,
-    ( "%d WINS server%s listed.\n", (int)count, (1==count)?"":"s" ) );
-
-  return( (count > 0) ? True : False );
-  } /* wins_srv_load_list */
-
-
-struct in_addr wins_srv_ip( void )
-  /* ------------------------------------------------------------------------ **
-   * Return the IP address of an NBNS (WINS) server thought to be active.
-   *
-   *  Input:  none.
-   *
-   *  Output: An IP address in struct in_addr form.
-   *
-   *  Notes:  This function will return the IP address of the first available
-   *          NBNS (WINS) server.  The order of the list is determined in
-   *          smb.conf.  If all of the WINS servers have been marked 'dead'
-   *          then the zero IP (0.0.0.0) is returned.  The zero IP is not a
-   *          valid Unicast address on any system.
-   *
-   * ------------------------------------------------------------------------ **
-   */
-  {
-  time_t      now     = time(NULL);
-  list_entry *entry   = (list_entry *)ubi_slFirst( wins_srv_list );
-
-  while( NULL != entry )
-    {
-    if( now >= entry->mourning )        /* Found a live one. */
-      {
-      /* If we don't have the IP, look it up. */
-      if( zero_ip( entry->ip_addr ) )
-        entry->ip_addr = *interpret_addr2( entry->server );
-
-      /* If we still don't have the IP then kill it, else return it. */
-      if( zero_ip( entry->ip_addr ) )
-        entry->mourning = now + NECROMANCYCLE;
-      else
-        return( entry->ip_addr );
-      }
-    entry = (list_entry *)ubi_slNext( entry );
-    }
-
-  /* If there are no live entries, return the zero IP. */
-  return( *interpret_addr2( "0.0.0.0" ) );
-  } /* wins_srv_ip */
-
-
-char *wins_srv_name( void )
-  /* ------------------------------------------------------------------------ **
-   * Return the name of first live WINS server in the list.
-   *
-   *  Input:  none.
-   *
-   *  Output: A pointer to a string containing either the DNS name or IP
-   *          address of the WINS server as given in the WINS SERVER
-   *          parameter in smb.conf, or NULL if no (live) WINS servers are
-   *          in the list.
-   *
-   *  Notes:  This function will return the name of the first available
-   *          NBNS (WINS) server.  The order of the list is determined in
-   *          smb.conf.  If all of the WINS servers have been marked 'dead'
-   *          then NULL is returned.
-   *
-   *        - This function does not verify that the name can be mapped to
-   *          an IP address, or that the WINS server is running.
-   *
-   * ------------------------------------------------------------------------ **
-   */
-  {
-  time_t      now     = time(NULL);
-  list_entry *entry   = (list_entry *)ubi_slFirst( wins_srv_list );
-   
-  while( NULL != entry )
-    {
-    if( now >= entry->mourning )
-      return( entry->server );          /* Found a live one. */
-    entry = (list_entry *)ubi_slNext( entry );
-    }
-   
-  /* If there are no live entries, return NULL. */
-  return( NULL );
-  } /* wins_srv_name */
-
-
-void wins_srv_died( struct in_addr boothill_ip )
-  /* ------------------------------------------------------------------------ **
-   * Called to indicate that a specific WINS server has died.
-   *
-   *  Input:  boothill_ip - IP address of an NBNS (WINS) server that has
-   *                        failed.
-   *
-   *  Notes:  This function marks the record 'dead' for NECROMANCYCLE
-   *          seconds.  
-   *
-   * ------------------------------------------------------------------------ **
-   */
-  {
-  list_entry *entry;
-
-  if( zero_ip( boothill_ip ) )
-    {
-    DEBUG( 4, ("wins_srv_died(): Invalid request to mark zero IP down.\n") );
-    return;
-    }
-
-  entry = (list_entry *)ubi_slFirst( wins_srv_list );
-  while( NULL != entry )
-    {
-    /* Match based on IP. */
-    if( ip_equal( boothill_ip, entry->ip_addr ) )
-      {
-      entry->mourning = time(NULL) + NECROMANCYCLE;
-      entry->ip_addr.s_addr = 0;  /* Force a re-lookup at re-birth. */
-      DEBUG( 2, ( "wins_srv_died(): WINS server %s appears to be down.\n", 
-                  entry->server ) );
-      return;
-      }
-    entry = (list_entry *)ubi_slNext( entry );
-    }
-
-  if( DEBUGLVL( 1 ) )
-    {
-    dbgtext( "wins_srv_died(): Could not mark WINS server %s down.\n",
-              inet_ntoa( boothill_ip ) );
-    dbgtext( "Address not found in server list.\n" );
-    }
-  } /* wins_srv_died */
-
-
-unsigned long wins_srv_count( void )
-  /* ------------------------------------------------------------------------ **
-   * Return the total number of entries in the list, dead or alive.
-   * ------------------------------------------------------------------------ **
-   */
-  {
-  unsigned long count = ubi_slCount( wins_srv_list );
-
-  if( DEBUGLVL( 8 ) )
-    {
-    list_entry *entry = (list_entry *)ubi_slFirst( wins_srv_list );
-    time_t      now   = time(NULL);
-
-    dbgtext( "wins_srv_count: WINS status: %ld servers.\n", count );
-    while( NULL != entry )
-      {
-      dbgtext( "  %s <%s>: ", entry->server, inet_ntoa( entry->ip_addr ) );
-      if( now >= entry->mourning )
-        dbgtext( "alive\n" );
-      else
-        dbgtext( "dead for %d more seconds\n", (int)(entry->mourning - now) );
-
-      entry = (list_entry *)ubi_slNext( entry );
-      }
-    }
-
-  return( count );
-  } /* wins_srv_count */
-
-/* ========================================================================== */
+/*
+  This is pretty much a complete rewrite of the earlier code. The main
+  aim of the rewrite is to add support for having multiple wins server
+  lists, so Samba can register with multiple groups of wins servers
+  and each group has a failover list of wins servers.
+
+  Central to the way it all works is the idea of a wins server
+  'tag'. A wins tag is a label for a group of wins servers. For
+  example if you use
+
+      wins server = fred:192.168.2.10 mary:192.168.3.199 fred:192.168.2.61
+
+  then you would have two groups of wins servers, one tagged with the
+  name 'fred' and the other with the name 'mary'. I would usually
+  recommend using interface names instead of 'fred' and 'mary' but
+  they can be any alpha string.
+
+  Now, how does it all work. Well, nmbd needs to register each of its
+  IPs with each of its names once with each group of wins servers. So
+  it tries registering with the first one mentioned in the list, then
+  if that fails it marks that WINS server dead and moves onto the next
+  one. 
+
+  In the client code things are a bit different. As each of the groups
+  of wins servers is a separate name space we need to try each of the
+  groups until we either succeed or we run out of wins servers to
+  try. If we get a negative response from a wins server then that
+  means the name doesn't exist in that group, so we give up on that
+  group and move to the next group. If we don't get a response at all
+  then maybe the wins server is down, in which case we need to
+  failover to the next one for that group.
+
+  confused yet? (tridge)
+*/
+
+/* how long a server is marked dead for */
+#define DEATH_TIME 600
+
+/* The list of dead wins servers is stored in gencache.tdb.  Each server is
+   marked dead from the point of view of a given source address. We keep a 
+   separate dead list for each src address to cope with multiple interfaces 
+   that are not routable to each other.
+  */
+
+#define WINS_SRV_FMT "WINS_SRV_DEAD/%s,%s" /* wins_ip,src_ip */
+
+static char *wins_srv_keystr(struct in_addr wins_ip, struct in_addr src_ip)
+{
+       char *keystr = NULL, *wins_ip_addr = NULL, *src_ip_addr = NULL;
+
+       wins_ip_addr = strdup(inet_ntoa(wins_ip));
+       src_ip_addr = strdup(inet_ntoa(src_ip));
+
+       if ( !wins_ip_addr || !src_ip_addr ) {
+               DEBUG(0,("wins_srv_keystr: malloc error\n"));
+               goto done;
+       }
+
+       if (asprintf(&keystr, WINS_SRV_FMT, wins_ip_addr, src_ip_addr) == -1) {
+               DEBUG(0, (": ns_srv_keystr: malloc error for key string\n"));
+       }
+
+done:
+       SAFE_FREE(wins_ip_addr);
+       SAFE_FREE(src_ip_addr);
+
+       return keystr;
+}
+
+/*
+  see if an ip is on the dead list
+*/
+
+BOOL wins_srv_is_dead(struct in_addr wins_ip, struct in_addr src_ip)
+{
+       char *keystr = wins_srv_keystr(wins_ip, src_ip);
+       BOOL result;
+
+       /* If the key exists then the WINS server has been marked as dead */
+
+       result = gencache_get(keystr, NULL, NULL);
+       SAFE_FREE(keystr);
+
+       DEBUG(4, ("wins_srv_is_dead: %s is %s\n", inet_ntoa(wins_ip),
+                 result ? "dead" : "alive"));
+
+       return result;
+}
+
+
+/*
+  mark a wins server as being alive (for the moment)
+*/
+void wins_srv_alive(struct in_addr wins_ip, struct in_addr src_ip)
+{
+       char *keystr = wins_srv_keystr(wins_ip, src_ip);
+
+       gencache_del(keystr);
+       SAFE_FREE(keystr);
+
+       DEBUG(4, ("wins_srv_alive: marking wins server %s alive\n", 
+                 inet_ntoa(wins_ip)));
+}
+
+/*
+  mark a wins server as temporarily dead
+*/
+void wins_srv_died(struct in_addr wins_ip, struct in_addr src_ip)
+{
+       char *keystr;
+
+       if (is_zero_ip(wins_ip) || wins_srv_is_dead(wins_ip, src_ip))
+               return;
+
+       keystr = wins_srv_keystr(wins_ip, src_ip);
+
+       gencache_set(keystr, "DOWN", time(NULL) + DEATH_TIME);
+
+       SAFE_FREE(keystr);
+
+       DEBUG(4,("Marking wins server %s dead for %u seconds from source %s\n",
+                inet_ntoa(wins_ip), DEATH_TIME, inet_ntoa(src_ip)));
+}
+
+/*
+  return the total number of wins servers, dead or not
+*/
+unsigned wins_srv_count(void)
+{
+       const char **list;
+       int count = 0;
+
+       if (lp_wins_support()) {
+               /* simple - just talk to ourselves */
+               return 1;
+       }
+
+       list = lp_wins_server_list();
+       for (count=0; list && list[count]; count++)
+               /* nop */ ;
+
+       return count;
+}
+
+/* an internal convenience structure for an IP with a short string tag
+   attached */
+struct tagged_ip {
+       fstring tag;
+       struct in_addr ip;
+};
+
+/*
+  parse an IP string that might be in tagged format
+  the result is a tagged_ip structure containing the tag
+  and the ip in in_addr format. If there is no tag then
+  use the tag '*'
+*/
+static void parse_ip(struct tagged_ip *ip, const char *str)
+{
+       char *s = strchr(str, ':');
+       if (!s) {
+               fstrcpy(ip->tag, "*");
+               ip->ip = *interpret_addr2(str);
+               return;
+       } 
+
+       ip->ip = *interpret_addr2(s+1);
+       fstrcpy(ip->tag, str);
+       s = strchr(ip->tag, ':');
+       if (s) *s = 0;
+}
+
+
+
+/*
+  return the list of wins server tags. A 'tag' is used to distinguish
+  wins server as either belonging to the same name space or a separate
+  name space. Usually you would setup your 'wins server' option to
+  list one or more wins server per interface and use the interface
+  name as your tag, but you are free to use any tag you like.
+*/
+char **wins_srv_tags(void)
+{
+       char **ret = NULL;
+       int count=0, i, j;
+       const char **list;
+
+       if (lp_wins_support()) {
+               /* give the caller something to chew on. This makes
+                  the rest of the logic simpler (ie. less special cases) */
+               ret = (char **)malloc(sizeof(char *)*2);
+               if (!ret) return NULL;
+               ret[0] = strdup("*");
+               ret[1] = NULL;
+               return ret;
+       }
+
+       list = lp_wins_server_list();
+       if (!list)
+               return NULL;
+
+       /* yes, this is O(n^2) but n is very small */
+       for (i=0;list[i];i++) {
+               struct tagged_ip t_ip;
+               
+               parse_ip(&t_ip, list[i]);
+
+               /* see if we already have it */
+               for (j=0;j<count;j++) {
+                       if (strcmp(ret[j], t_ip.tag) == 0) {
+                               break;
+                       }
+               }
+
+               if (j != count) {
+                       /* we already have it. Move along */
+                       continue;
+               }
+
+               /* add it to the list */
+               ret = (char **)Realloc(ret, (count+2) * sizeof(char *));
+               ret[count] = strdup(t_ip.tag);
+               if (!ret[count]) break;
+               count++;
+       }
+
+       if (count) {
+               /* make sure we null terminate */
+               ret[count] = NULL;
+       }
+
+       return ret;
+}
+
+/* free a list of wins server tags given by wins_srv_tags */
+void wins_srv_tags_free(char **list)
+{
+       int i;
+       if (!list) return;
+       for (i=0; list[i]; i++) {
+               free(list[i]);
+       }
+       free(list);
+}
+
+
+/*
+  return the IP of the currently active wins server for the given tag,
+  or the zero IP otherwise
+*/
+struct in_addr wins_srv_ip_tag(const char *tag, struct in_addr src_ip)
+{
+       const char **list;
+       int i;
+       struct tagged_ip t_ip;
+
+       /* if we are a wins server then we always just talk to ourselves */
+       if (lp_wins_support()) {
+               extern struct in_addr loopback_ip;
+               return loopback_ip;
+       }
+
+       list = lp_wins_server_list();
+       if (!list || !list[0]) {
+               struct in_addr ip;
+               zero_ip(&ip);
+               return ip;
+       }
+
+       /* find the first live one for this tag */
+       for (i=0; list[i]; i++) {
+               parse_ip(&t_ip, list[i]);
+               if (strcmp(tag, t_ip.tag) != 0) {
+                       /* not for the right tag. Move along */
+                       continue;
+               }
+               if (!wins_srv_is_dead(t_ip.ip, src_ip)) {
+                       fstring src_name;
+                       fstrcpy(src_name, inet_ntoa(src_ip));
+                       DEBUG(6,("Current wins server for tag '%s' with source %s is %s\n", 
+                                tag, 
+                                src_name,
+                                inet_ntoa(t_ip.ip)));
+                       return t_ip.ip;
+               }
+       }
+       
+       /* they're all dead - try the first one until they revive */
+       for (i=0; list[i]; i++) {
+               parse_ip(&t_ip, list[i]);
+               if (strcmp(tag, t_ip.tag) != 0) {
+                       continue;
+               }
+               return t_ip.ip;
+       }
+
+       /* this can't happen?? */
+       zero_ip(&t_ip.ip);
+       return t_ip.ip;
+}
+
+
+/*
+  return a count of the number of IPs for a particular tag, including
+  dead ones
+*/
+unsigned wins_srv_count_tag(const char *tag)
+{
+       const char **list;
+       int i, count=0;
+
+       /* if we are a wins server then we always just talk to ourselves */
+       if (lp_wins_support()) {
+               return 1;
+       }
+
+       list = lp_wins_server_list();
+       if (!list || !list[0]) {
+               return 0;
+       }
+
+       /* find the first live one for this tag */
+       for (i=0; list[i]; i++) {
+               struct tagged_ip t_ip;
+               parse_ip(&t_ip, list[i]);
+               if (strcmp(tag, t_ip.tag) == 0) {
+                       count++;
+               }
+       }
+
+       return count;
+}