What to do about debugging in a multi-threaded application?
[ira/wip.git] / source3 / lib / wins_srv.c
index e84ac342f5d094ce7e81ce037589e87e840c3bf5..b2c0bf8b870613f3ca4876b976beb67bde31b831 100644 (file)
@@ -1,12 +1,13 @@
 /*
-   Unix SMB/Netbios implementation.
-   Version 1.9.
-   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
-   the Free Software Foundation; either version 2 of the License, or
+   the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.
 
    This program is distributed in the hope that it will be useful,
    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.
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
 #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 greater than current time server is dead, Jim.  */
-  char      *server;    /* DNS name or IP of NBNS server to query.            */
-  } 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.
-   */
-  {
-  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: 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;
-      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. */
-        (void)ubi_slAddTail( wins_srv_list, entry );
-        DEBUGADD( 4, ("\t\t%s,\n", wins_id_bufr) );
-        }
-      }
-    }
-
-  count = ubi_slCount( wins_srv_list );
-  DEBUGADD( 4, ( "\t\t%d WINS server%s listed.\n", count, (1==count)?"":"s" ) );
-
-  return( (count > 0) ? True : False );
-  } /* wins_srv_load_list */
-
-
-char *wins_srv( void )
-  /* ------------------------------------------------------------------------ **
-   */
-  {
-  time_t      now     = time(NULL);
-  list_entry *entry   = (list_entry *)ubi_slFirst( wins_srv_list );
-  list_entry *coldest = entry;
-
-  /* Go through the list.  Look for the first live entry. */
-  while( (NULL != entry) && (now < entry->mourning) )
-    {
-    entry = (list_entry *)ubi_slNext( entry );
-    if( entry->mourning < coldest->mourning )
-      coldest = entry;
-    }
-
-  /* If they were all dead, then return the one that's been dead longest. */
-  if( NULL == entry )
-    {
-    entry = coldest;
-    DEBUG( 4, ("wins_srv: All WINS servers appear to have failed.\n") );
-    }
-
-  /* The list could be empty.  Check it. */
-  if( NULL == entry )
-    return( NULL );
-  return( entry->server );
-  } /* wins_srv */
-
-
-void wins_srv_died( char *boothill )
-  /* ------------------------------------------------------------------------ **
-   * Called to indicate that a specific WINS server has died.
-   */
-  {
-  list_entry *entry = (list_entry *)ubi_slFirst( wins_srv_list );
-
-  while( NULL != entry )
-    {
-    /* Match based on server ID [DNS name or IP]. */
-    if( 0 == strcmp( boothill, entry->server ) )
-      {
-      entry->mourning = time(NULL) + NECROMANCYCLE;
-      DEBUG( 2, ("wins_srv: WINS server %s appears to be down.\n", boothill) );
-      return;
-      }
-    entry = (list_entry *)ubi_slNext( entry );
-    }
-  } /* wins_srv_died */
-
-
-unsigned long wins_srv_count( void )
-  /* ------------------------------------------------------------------------ **
-   * Return the total number of entries in the list, dead or alive.
-   */
-  {
-  return( ubi_slCount( wins_srv_list ) );
-  } /* 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 = SMB_STRDUP(inet_ntoa(wins_ip));
+       src_ip_addr = SMB_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_v4(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 = SMB_MALLOC_ARRAY(char *, 2);
+               if (!ret) return NULL;
+               ret[0] = SMB_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 = SMB_REALLOC_ARRAY(ret, char *, count+2);
+               if (!ret) {
+                       return NULL;
+               }
+               ret[count] = SMB_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()) {
+               struct in_addr loopback_ip;
+               loopback_ip.s_addr = htonl(INADDR_LOOPBACK);
+               return loopback_ip;
+       }
+
+       list = lp_wins_server_list();
+       if (!list || !list[0]) {
+               struct in_addr ip;
+               zero_ip_v4(&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_v4(&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;
+}