summaryrefslogtreecommitdiff
path: root/source3
diff options
context:
space:
mode:
authorGerald Carter <jerry@samba.org>2006-02-03 21:19:24 +0000
committerGerald (Jerry) Carter <jerry@samba.org>2007-10-10 11:06:23 -0500
commit855e02f1649992f05b685be96dfff4a9140170e9 (patch)
treef3224a28b08d3d569f929039e0bd7313ab81dbd9 /source3
parent989c9311c5c155a8bea2b7921edf5eeb36c54ead (diff)
downloadsamba-855e02f1649992f05b685be96dfff4a9140170e9.tar.gz
samba-855e02f1649992f05b685be96dfff4a9140170e9.tar.bz2
samba-855e02f1649992f05b685be96dfff4a9140170e9.zip
r13310: first round of server affinity patches for winbindd & net ads join
(This used to be commit 6c3480f9aecc061660ad5c06347b8f1d3e11a330)
Diffstat (limited to 'source3')
-rw-r--r--source3/include/smb.h8
-rw-r--r--source3/lib/gencache.c20
-rw-r--r--source3/libads/ldap.c4
-rw-r--r--source3/libsmb/cliconnect.c12
-rw-r--r--source3/libsmb/namequery.c323
-rw-r--r--source3/libsmb/namequery_dc.c28
-rw-r--r--source3/nsswitch/winbindd_cm.c70
-rw-r--r--source3/passdb/secrets.c29
8 files changed, 275 insertions, 219 deletions
diff --git a/source3/include/smb.h b/source3/include/smb.h
index b8211aac45..3a6f68b9ec 100644
--- a/source3/include/smb.h
+++ b/source3/include/smb.h
@@ -238,14 +238,6 @@ typedef struct nttime_info {
#define MAX_HOURS_LEN 32
-/*
- * window during which we must talk to the PDC to avoid
- * sam sync delays; expressed in seconds (15 minutes is the
- * default period for SAM replication under Windows NT 4.0
- */
-#define SAM_SYNC_WINDOW 900
-
-
#ifndef MAXSUBAUTHS
#define MAXSUBAUTHS 15 /* max sub authorities in a SID */
#endif
diff --git a/source3/lib/gencache.c b/source3/lib/gencache.c
index fd44616270..89badcd4f9 100644
--- a/source3/lib/gencache.c
+++ b/source3/lib/gencache.c
@@ -268,7 +268,7 @@ BOOL gencache_get(const char *keystr, char **valstr, time_t *timeout)
SAFE_FREE(entry_buf);
DEBUG(10, ("Returning %s cache entry: key = %s, value = %s, "
- "timeout = %s\n", t > time(NULL) ? "valid" :
+ "timeout = %s", t > time(NULL) ? "valid" :
"expired", keystr, v, ctime(&t)));
if (valstr)
@@ -281,20 +281,18 @@ BOOL gencache_get(const char *keystr, char **valstr, time_t *timeout)
return t > time(NULL);
- } else {
- SAFE_FREE(databuf.dptr);
+ }
- if (valstr)
- *valstr = NULL;
+ SAFE_FREE(databuf.dptr);
- if (timeout)
- timeout = NULL;
+ if (valstr)
+ *valstr = NULL;
+ if (timeout)
+ timeout = NULL;
- DEBUG(10, ("Cache entry with key = %s couldn't be found\n",
- keystr));
+ DEBUG(10, ("Cache entry with key = %s couldn't be found\n", keystr));
- return False;
- }
+ return False;
}
diff --git a/source3/libads/ldap.c b/source3/libads/ldap.c
index dc93bd556c..e503da62a4 100644
--- a/source3/libads/ldap.c
+++ b/source3/libads/ldap.c
@@ -136,6 +136,10 @@ BOOL ads_try_connect(ADS_STRUCT *ads, const char *server, unsigned port)
ads->ldap_port = port;
ads->ldap_ip = *interpret_addr2(srv);
free(srv);
+
+ /* cache the successful connection */
+
+ saf_store( ads->server.workgroup, server );
return True;
}
diff --git a/source3/libsmb/cliconnect.c b/source3/libsmb/cliconnect.c
index ac7d1b1650..7c15c8d19f 100644
--- a/source3/libsmb/cliconnect.c
+++ b/source3/libsmb/cliconnect.c
@@ -865,14 +865,16 @@ BOOL cli_session_setup(struct cli_state *cli,
DEBUG(3, ("SPNEGO login failed: %s\n", ads_errstr(status)));
return False;
}
- return True;
+ } else {
+ /* otherwise do a NT1 style session setup */
+ if ( !cli_session_setup_nt1(cli, user, pass, passlen, ntpass, ntpasslen, workgroup) ) {
+ DEBUG(3,("cli_session_setup: NT1 session setup failed!\n"));
+ return False;
+ }
}
- /* otherwise do a NT1 style session setup */
+ return True;
- return cli_session_setup_nt1(cli, user,
- pass, passlen, ntpass, ntpasslen,
- workgroup);
}
/****************************************************************************
diff --git a/source3/libsmb/namequery.c b/source3/libsmb/namequery.c
index 28b89db908..0986e7f29a 100644
--- a/source3/libsmb/namequery.c
+++ b/source3/libsmb/namequery.c
@@ -24,6 +24,94 @@
/* nmbd.c sets this to True. */
BOOL global_in_nmbd = False;
+
+/****************************
+ * SERVER AFFINITY ROUTINES *
+ ****************************/
+
+ /* Server affinity is the concept of preferring the last domain
+ controller with whom you had a successful conversation */
+
+/****************************************************************************
+****************************************************************************/
+#define SAFKEY_FMT "SAF/DOMAIN/%s"
+#define SAF_TTL 900
+
+static char *saf_key(const char *domain)
+{
+ char *keystr;
+
+ asprintf( &keystr, SAFKEY_FMT, strupper_static(domain) );
+
+ return keystr;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+BOOL saf_store( const char *domain, const char *servername )
+{
+ char *key;
+ time_t expire;
+ BOOL ret = False;
+
+ if ( !domain || !servername ) {
+ DEBUG(2,("saf_store: Refusing to store empty domain or servername!\n"));
+ return False;
+ }
+
+ if ( !gencache_init() )
+ return False;
+
+ key = saf_key( domain );
+ expire = time( NULL ) + SAF_TTL;
+
+
+ DEBUG(10,("saf_store: domain = [%s], server = [%s], expire = [%d]\n",
+ domain, servername, expire ));
+
+ ret = gencache_set( key, servername, expire );
+
+ SAFE_FREE( key );
+
+ return ret;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+char *saf_fetch( const char *domain )
+{
+ char *server = NULL;
+ time_t timeout;
+ BOOL ret = False;
+ char *key = NULL;
+
+ if ( !domain ) {
+ DEBUG(2,("saf_fetch: Empty domain name!\n"));
+ return NULL;
+ }
+
+ if ( !gencache_init() )
+ return False;
+
+ key = saf_key( domain );
+
+ ret = gencache_get( key, &server, &timeout );
+
+ SAFE_FREE( key );
+
+ if ( !ret ) {
+ DEBUG(5,("saf_fetch: failed to find server for \"%s\" domain\n", domain ));
+ } else {
+ DEBUG(5,("saf_fetch: Returning \"%s\" for \"%s\" domain\n",
+ server, domain ));
+ }
+
+ return server;
+}
+
+
/****************************************************************************
Generate a random trn_id.
****************************************************************************/
@@ -1261,6 +1349,18 @@ static BOOL get_dc_list(const char *domain, struct ip_service **ip_list,
int *count, BOOL ads_only, int *ordered)
{
fstring resolve_order;
+ char *saf_servername;
+ pstring pserver;
+ const char *p;
+ char *port_str;
+ int port;
+ fstring name;
+ int num_addresses = 0;
+ int local_count, i, j;
+ struct ip_service *return_iplist = NULL;
+ struct ip_service *auto_ip_list = NULL;
+ BOOL done_auto_lookup = False;
+ int auto_count = 0;
/* if we are restricted to solely using DNS for looking
up a domain controller, make sure that host lookups
@@ -1277,148 +1377,145 @@ static BOOL get_dc_list(const char *domain, struct ip_service **ip_list,
fstrcpy( resolve_order, "NULL" );
}
-
*ordered = False;
-
- /* If it's our domain then use the 'password server' parameter. */
-
+
+ /* fetch the server we have affinity for. Add the
+ 'password server' list to a search for our domain controllers */
+
+ saf_servername = saf_fetch( domain );
+
if ( strequal(domain, lp_workgroup()) || strequal(domain, lp_realm()) ) {
- const char *p;
- char *pserver = lp_passwordserver(); /* UNIX charset. */
- char *port_str;
- int port;
- fstring name;
- int num_addresses = 0;
- int local_count, i, j;
- struct ip_service *return_iplist = NULL;
- struct ip_service *auto_ip_list = NULL;
- BOOL done_auto_lookup = False;
- int auto_count = 0;
-
+ pstr_sprintf( pserver, "%s, %s",
+ saf_servername ? saf_servername : "",
+ lp_passwordserver() );
+ } else {
+ pstr_sprintf( pserver, "%s, *",
+ saf_servername ? saf_servername : "" );
+ }
- if (!*pserver)
- return internal_resolve_name(domain, 0x1C, ip_list, count, resolve_order);
+ SAFE_FREE( saf_servername );
- p = pserver;
-
- /*
- * if '*' appears in the "password server" list then add
- * an auto lookup to the list of manually configured
- * DC's. If any DC is listed by name, then the list should be
- * considered to be ordered
- */
-
- while (next_token(&p,name,LIST_SEP,sizeof(name))) {
- if (strequal(name, "*")) {
- if ( internal_resolve_name(domain, 0x1C, &auto_ip_list, &auto_count, resolve_order) )
- num_addresses += auto_count;
- done_auto_lookup = True;
- DEBUG(8,("Adding %d DC's from auto lookup\n", auto_count));
- } else {
- num_addresses++;
- }
+ /* if we are starting from scratch, just lookup DOMAIN<0x1c> */
+
+ if ( !*pserver ) {
+ DEBUG(10,("get_dc_list: no preferred domain controllers.\n"));
+ return internal_resolve_name(domain, 0x1C, ip_list, count, resolve_order);
+ }
+
+ DEBUG(3,("get_dc_list: preferred server list: \"%s\"\n", pserver ));
+
+ /*
+ * if '*' appears in the "password server" list then add
+ * an auto lookup to the list of manually configured
+ * DC's. If any DC is listed by name, then the list should be
+ * considered to be ordered
+ */
+
+ p = pserver;
+ while (next_token(&p,name,LIST_SEP,sizeof(name))) {
+ if (strequal(name, "*")) {
+ if ( internal_resolve_name(domain, 0x1C, &auto_ip_list, &auto_count, resolve_order) )
+ num_addresses += auto_count;
+ done_auto_lookup = True;
+ DEBUG(8,("Adding %d DC's from auto lookup\n", auto_count));
+ } else {
+ num_addresses++;
}
+ }
- /* if we have no addresses and haven't done the auto lookup, then
- just return the list of DC's */
+ /* if we have no addresses and haven't done the auto lookup, then
+ just return the list of DC's. Or maybe we just failed. */
- if ( (num_addresses == 0) && !done_auto_lookup ) {
+ if ( (num_addresses == 0) ) {
+ if ( !done_auto_lookup ) {
return internal_resolve_name(domain, 0x1C, ip_list, count, resolve_order);
- }
-
- /* maybe we just failed? */
-
- if ( num_addresses == 0 ) {
- DEBUG(4,("get_dc_list: no servers found\n"));
- return False;
- }
-
- if ( (return_iplist = SMB_MALLOC_ARRAY(struct ip_service, num_addresses)) == NULL ) {
- DEBUG(3,("get_dc_list: malloc fail !\n"));
+ } else {
+ DEBUG(4,("get_dc_list: no servers found\n"));
return False;
}
+ }
+
+ if ( (return_iplist = SMB_MALLOC_ARRAY(struct ip_service, num_addresses)) == NULL ) {
+ DEBUG(3,("get_dc_list: malloc fail !\n"));
+ return False;
+ }
- p = pserver;
- local_count = 0;
+ p = pserver;
+ local_count = 0;
- /* fill in the return list now with real IP's */
+ /* fill in the return list now with real IP's */
- while ( (local_count<num_addresses) && next_token(&p,name,LIST_SEP,sizeof(name)) ) {
- struct in_addr name_ip;
+ while ( (local_count<num_addresses) && next_token(&p,name,LIST_SEP,sizeof(name)) ) {
+ struct in_addr name_ip;
- /* copy any addersses from the auto lookup */
+ /* copy any addersses from the auto lookup */
- if ( strequal(name, "*") ) {
- for ( j=0; j<auto_count; j++ ) {
- /* Check for and don't copy any known bad DC IP's. */
- if(!NT_STATUS_IS_OK(check_negative_conn_cache(domain,
- inet_ntoa(auto_ip_list[j].ip)))) {
- DEBUG(5,("get_dc_list: negative entry %s removed from DC list\n",
- inet_ntoa(auto_ip_list[j].ip) ));
- continue;
- }
- return_iplist[local_count].ip = auto_ip_list[j].ip;
- return_iplist[local_count].port = auto_ip_list[j].port;
- local_count++;
+ if ( strequal(name, "*") ) {
+ for ( j=0; j<auto_count; j++ ) {
+ /* Check for and don't copy any known bad DC IP's. */
+ if(!NT_STATUS_IS_OK(check_negative_conn_cache(domain,
+ inet_ntoa(auto_ip_list[j].ip)))) {
+ DEBUG(5,("get_dc_list: negative entry %s removed from DC list\n",
+ inet_ntoa(auto_ip_list[j].ip) ));
+ continue;
}
- continue;
+ return_iplist[local_count].ip = auto_ip_list[j].ip;
+ return_iplist[local_count].port = auto_ip_list[j].port;
+ local_count++;
}
+ continue;
+ }
- /* added support for address:port syntax for ads (not that I think
- anyone will ever run the LDAP server in an AD domain on something
- other than port 389 */
+ /* added support for address:port syntax for ads (not that I think
+ anyone will ever run the LDAP server in an AD domain on something
+ other than port 389 */
- port = (lp_security() == SEC_ADS) ? LDAP_PORT : PORT_NONE;
- if ( (port_str=strchr(name, ':')) != NULL ) {
- *port_str = '\0';
- port_str++;
- port = atoi( port_str );
- }
-
- /* explicit lookup; resolve_name() will handle names & IP addresses */
- if ( resolve_name( name, &name_ip, 0x20 ) ) {
+ port = (lp_security() == SEC_ADS) ? LDAP_PORT : PORT_NONE;
+ if ( (port_str=strchr(name, ':')) != NULL ) {
+ *port_str = '\0';
+ port_str++;
+ port = atoi( port_str );
+ }
- /* Check for and don't copy any known bad DC IP's. */
- if( !NT_STATUS_IS_OK(check_negative_conn_cache(domain, inet_ntoa(name_ip))) ) {
- DEBUG(5,("get_dc_list: negative entry %s removed from DC list\n",name ));
- continue;
- }
+ /* explicit lookup; resolve_name() will handle names & IP addresses */
+ if ( resolve_name( name, &name_ip, 0x20 ) ) {
- return_iplist[local_count].ip = name_ip;
- return_iplist[local_count].port = port;
- local_count++;
- *ordered = True;
+ /* Check for and don't copy any known bad DC IP's. */
+ if( !NT_STATUS_IS_OK(check_negative_conn_cache(domain, inet_ntoa(name_ip))) ) {
+ DEBUG(5,("get_dc_list: negative entry %s removed from DC list\n",name ));
+ continue;
}
+
+ return_iplist[local_count].ip = name_ip;
+ return_iplist[local_count].port = port;
+ local_count++;
+ *ordered = True;
}
+ }
- SAFE_FREE(auto_ip_list);
+ SAFE_FREE(auto_ip_list);
- /* need to remove duplicates in the list if we have any
- explicit password servers */
-
- if ( local_count ) {
- local_count = remove_duplicate_addrs2( return_iplist, local_count );
- }
+ /* need to remove duplicates in the list if we have any
+ explicit password servers */
+
+ if ( local_count ) {
+ local_count = remove_duplicate_addrs2( return_iplist, local_count );
+ }
- if ( DEBUGLEVEL >= 4 ) {
- DEBUG(4,("get_dc_list: returning %d ip addresses in an %sordered list\n", local_count,
- *ordered ? "":"un"));
- DEBUG(4,("get_dc_list: "));
- for ( i=0; i<local_count; i++ )
- DEBUGADD(4,("%s:%d ", inet_ntoa(return_iplist[i].ip), return_iplist[i].port ));
- DEBUGADD(4,("\n"));
- }
+ if ( DEBUGLEVEL >= 4 ) {
+ DEBUG(4,("get_dc_list: returning %d ip addresses in an %sordered list\n", local_count,
+ *ordered ? "":"un"));
+ DEBUG(4,("get_dc_list: "));
+ for ( i=0; i<local_count; i++ )
+ DEBUGADD(4,("%s:%d ", inet_ntoa(return_iplist[i].ip), return_iplist[i].port ));
+ DEBUGADD(4,("\n"));
+ }
- *ip_list = return_iplist;
- *count = local_count;
+ *ip_list = return_iplist;
+ *count = local_count;
- return (*count != 0);
- }
-
- DEBUG(10,("get_dc_list: defaulting to internal auto lookup for domain %s\n", domain));
-
- return internal_resolve_name(domain, 0x1C, ip_list, count, resolve_order);
+ return (*count != 0);
}
/*********************************************************************
diff --git a/source3/libsmb/namequery_dc.c b/source3/libsmb/namequery_dc.c
index 0c9f19313c..b9a593bf2a 100644
--- a/source3/libsmb/namequery_dc.c
+++ b/source3/libsmb/namequery_dc.c
@@ -75,31 +75,10 @@ static BOOL rpc_dc_name(const char *domain, fstring srv_name, struct in_addr *ip
struct ip_service *ip_list = NULL;
struct in_addr dc_ip, exclude_ip;
int count, i;
- BOOL use_pdc_only;
NTSTATUS result;
zero_ip(&exclude_ip);
- use_pdc_only = must_use_pdc(domain);
-
- /* Lookup domain controller name */
-
- if ( use_pdc_only && get_pdc_ip(domain, &dc_ip) )
- {
- DEBUG(10,("rpc_dc_name: Atempting to lookup PDC to avoid sam sync delays\n"));
-
- /* check the connection cache and perform the node status
- lookup only if the IP is not found to be bad */
-
- if (name_status_find(domain, 0x1b, 0x20, dc_ip, srv_name) ) {
- result = check_negative_conn_cache( domain, srv_name );
- if ( NT_STATUS_IS_OK(result) )
- goto done;
- }
- /* Didn't get name, remember not to talk to this DC. */
- exclude_ip = dc_ip;
- }
-
/* get a list of all domain controllers */
if ( !get_sorted_dc_list(domain, &ip_list, &count, False) ) {
@@ -109,13 +88,6 @@ static BOOL rpc_dc_name(const char *domain, fstring srv_name, struct in_addr *ip
/* Remove the entry we've already failed with (should be the PDC). */
- if ( use_pdc_only ) {
- for (i = 0; i < count; i++) {
- if (ip_equal( exclude_ip, ip_list[i].ip))
- zero_ip(&ip_list[i].ip);
- }
- }
-
for (i = 0; i < count; i++) {
if (is_zero_ip(ip_list[i].ip))
continue;
diff --git a/source3/nsswitch/winbindd_cm.c b/source3/nsswitch/winbindd_cm.c
index 2ac984176c..177ac54d3e 100644
--- a/source3/nsswitch/winbindd_cm.c
+++ b/source3/nsswitch/winbindd_cm.c
@@ -358,6 +358,10 @@ static NTSTATUS cm_prepare_connection(const struct winbindd_domain *domain,
session_setup_done:
+ /* cache the server name for later connections */
+
+ saf_store( (*cli)->domain, (*cli)->desthost );
+
if (!cli_send_tconX(*cli, "IPC$", "IPC", "", 0)) {
result = cli_nt_error(*cli);
@@ -658,14 +662,6 @@ static BOOL get_dcs(TALLOC_CTX *mem_ctx, const struct winbindd_domain *domain,
return True;
}
- if ( is_our_domain
- && must_use_pdc(domain->name)
- && get_pdc_ip(domain->name, &ip))
- {
- if (add_one_dc_unique(mem_ctx, domain->name, inet_ntoa(ip), ip, dcs, num_dcs))
- return True;
- }
-
/* try standard netbios queries first */
get_sorted_dc_list(domain->name, &ip_list, &iplist_size, False);
@@ -752,12 +748,35 @@ static NTSTATUS cm_open_connection(struct winbindd_domain *domain,
{
TALLOC_CTX *mem_ctx;
NTSTATUS result;
-
+ char *saf_servername = saf_fetch( domain->name );
int retries;
if ((mem_ctx = talloc_init("cm_open_connection")) == NULL)
return NT_STATUS_NO_MEMORY;
+ /* we have to check the server affinity cache here since
+ later we selecte a DC based on response time and not preference */
+
+ if ( saf_servername )
+ {
+ /* convert an ip address to a name */
+ if ( is_ipaddress( saf_servername ) )
+ {
+ fstring saf_name;
+ struct in_addr ip;
+
+ ip = *interpret_addr2( saf_servername );
+ dcip_to_name( domain->name, domain->alt_name, &domain->sid, ip, saf_name );
+ fstrcpy( domain->dcname, saf_name );
+ }
+ else
+ {
+ fstrcpy( domain->dcname, saf_servername );
+ }
+
+ SAFE_FREE( saf_servername );
+ }
+
for (retries = 0; retries < 3; retries++) {
int fd = -1;
@@ -765,27 +784,28 @@ static NTSTATUS cm_open_connection(struct winbindd_domain *domain,
result = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND;
- if ((strlen(domain->dcname) > 0) &&
- NT_STATUS_IS_OK(check_negative_conn_cache(
- domain->name, domain->dcname)) &&
- (resolve_name(domain->dcname, &domain->dcaddr.sin_addr,
- 0x20))) {
- int dummy;
- struct sockaddr_in addrs[2];
- addrs[0] = domain->dcaddr;
- addrs[0].sin_port = htons(445);
- addrs[1] = domain->dcaddr;
- addrs[1].sin_port = htons(139);
- if (!open_any_socket_out(addrs, 2, 10000,
- &dummy, &fd)) {
+ if ((strlen(domain->dcname) > 0)
+ && NT_STATUS_IS_OK(check_negative_conn_cache( domain->name, domain->dcname))
+ && (resolve_name(domain->dcname, &domain->dcaddr.sin_addr, 0x20)))
+ {
+ struct sockaddr_in *addrs = NULL;
+ int num_addrs = 0;
+ int dummy = 0;
+
+
+ add_sockaddr_to_array(mem_ctx, domain->dcaddr.sin_addr, 445, &addrs, &num_addrs);
+ add_sockaddr_to_array(mem_ctx, domain->dcaddr.sin_addr, 139, &addrs, &num_addrs);
+
+ if (!open_any_socket_out(addrs, num_addrs, 10000, &dummy, &fd)) {
fd = -1;
}
}
- if ((fd == -1) &&
- !find_new_dc(mem_ctx, domain, domain->dcname,
- &domain->dcaddr, &fd))
+ if ((fd == -1)
+ && !find_new_dc(mem_ctx, domain, domain->dcname, &domain->dcaddr, &fd))
+ {
break;
+ }
new_conn->cli = NULL;
diff --git a/source3/passdb/secrets.c b/source3/passdb/secrets.c
index 14896a3340..88dabbd644 100644
--- a/source3/passdb/secrets.c
+++ b/source3/passdb/secrets.c
@@ -821,35 +821,6 @@ void secrets_named_mutex_release(const char *name)
DEBUG(10,("secrets_named_mutex: released mutex for %s\n", name ));
}
-/*********************************************************
- Check to see if we must talk to the PDC to avoid sam
- sync delays
- ********************************************************/
-
-BOOL must_use_pdc( const char *domain )
-{
- time_t now = time(NULL);
- time_t last_change_time;
- unsigned char passwd[16];
-
- if ( !secrets_fetch_trust_account_password(domain, passwd, &last_change_time, NULL) )
- return False;
-
- /*
- * If the time the machine password has changed
- * was less than about 15 minutes then we need to contact
- * the PDC only, as we cannot be sure domain replication
- * has yet taken place. Bug found by Gerald (way to go
- * Gerald !). JRA.
- */
-
- if ( now - last_change_time < SAM_SYNC_WINDOW )
- return True;
-
- return False;
-
-}
-
/*******************************************************************************
Store a complete AFS keyfile into secrets.tdb.
*******************************************************************************/