diff --git a/source3/Makefile.in b/source3/Makefile.in index bd09ee55c88..32c2e3f70f3 100644 --- a/source3/Makefile.in +++ b/source3/Makefile.in @@ -154,7 +154,8 @@ PARAM_OBJ = param/loadparm.o param/params.o dynconfig.o LIBADS_OBJ = libads/ldap.o libads/ldap_printer.o libads/sasl.o \ libads/krb5_setpw.o libads/kerberos.o libads/ldap_user.o \ libads/ads_struct.o libads/ads_status.o \ - libads/disp_sec.o libads/ads_utils.o + libads/disp_sec.o libads/ads_utils.o libads/ldap_utils.o \ + libads/ads_ldap.o LIBADS_SERVER_OBJ = libads/util.o libads/kerberos_verify.o diff --git a/source3/libads/ads_ldap.c b/source3/libads/ads_ldap.c new file mode 100644 index 00000000000..8c39a897625 --- /dev/null +++ b/source3/libads/ads_ldap.c @@ -0,0 +1,145 @@ +/* + Unix SMB/CIFS implementation. + + Winbind ADS backend functions + + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Andrew Bartlett 2002 + + 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. +*/ + +#include "includes.h" +#ifdef HAVE_LDAP + +/* convert a single name to a sid in a domain */ +NTSTATUS ads_name_to_sid(ADS_STRUCT *ads, + const char *name, + DOM_SID *sid, + enum SID_NAME_USE *type) +{ + const char *attrs[] = {"objectSid", "sAMAccountType", NULL}; + int count; + ADS_STATUS rc; + void *res = NULL; + char *exp; + uint32 t; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + + if (asprintf(&exp, "(|(sAMAccountName=%s)(userPrincipalName=%s@%s))", + name, name, ads->config.realm) == -1) { + DEBUG(1,("ads_name_to_sid: asprintf failed!\n")); + status = NT_STATUS_NO_MEMORY; + goto done; + } + + rc = ads_search_retry(ads, &res, exp, attrs); + free(exp); + if (!ADS_ERR_OK(rc)) { + DEBUG(1,("name_to_sid ads_search: %s\n", ads_errstr(rc))); + goto done; + } + + count = ads_count_replies(ads, res); + if (count != 1) { + DEBUG(1,("name_to_sid: %s not found\n", name)); + goto done; + } + + if (!ads_pull_sid(ads, res, "objectSid", sid)) { + DEBUG(1,("No sid for %s !?\n", name)); + goto done; + } + + if (!ads_pull_uint32(ads, res, "sAMAccountType", &t)) { + DEBUG(1,("No sAMAccountType for %s !?\n", name)); + goto done; + } + + *type = ads_atype_map(t); + + status = NT_STATUS_OK; + + DEBUG(3,("ads name_to_sid mapped %s\n", name)); + +done: + if (res) ads_msgfree(ads, res); + + return status; +} + +/* convert a sid to a user or group name */ +NTSTATUS ads_sid_to_name(ADS_STRUCT *ads, + TALLOC_CTX *mem_ctx, + DOM_SID *sid, + char **name, + enum SID_NAME_USE *type) +{ + const char *attrs[] = {"userPrincipalName", + "sAMAccountName", + "sAMAccountType", NULL}; + ADS_STATUS rc; + void *msg = NULL; + char *exp = NULL; + char *sidstr = NULL; + uint32 atype; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + + if (!(sidstr = sid_binstring(sid))) { + DEBUG(1,("ads_sid_to_name: sid_binstring failed!\n")); + status = NT_STATUS_NO_MEMORY; + goto done; + } + + if (asprintf(&exp, "(objectSid=%s)", sidstr) == -1) { + DEBUG(1,("ads_sid_to_name: asprintf failed!\n")); + status = NT_STATUS_NO_MEMORY; + goto done; + } + + rc = ads_search_retry(ads, &msg, exp, attrs); + if (!ADS_ERR_OK(rc)) { + status = ads_ntstatus(rc); + DEBUG(1,("ads_sid_to_name ads_search: %s\n", ads_errstr(rc))); + goto done; + } + + if (!ads_pull_uint32(ads, msg, "sAMAccountType", &atype)) { + goto done; + } + + *name = ads_pull_username(ads, mem_ctx, msg); + if (!*name) { + DEBUG(1,("ads_sid_to_name: ads_pull_username retuned NULL!\n")); + status = NT_STATUS_NO_MEMORY; + goto done; + } + + *type = ads_atype_map(atype); + + status = NT_STATUS_OK; + + DEBUG(3,("ads sid_to_name mapped %s\n", *name)); + +done: + if (msg) ads_msgfree(ads, msg); + + SAFE_FREE(exp); + SAFE_FREE(sidstr); + + return status; +} + +#endif diff --git a/source3/libads/ads_utils.c b/source3/libads/ads_utils.c index fc8a2700211..626c1779266 100644 --- a/source3/libads/ads_utils.c +++ b/source3/libads/ads_utils.c @@ -3,7 +3,8 @@ ads (active directory) utility library Copyright (C) Stefan (metze) Metzmacher 2002 - + Copyright (C) Andrew Tridgell 2001 + 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 @@ -21,9 +22,6 @@ #include "includes.h" -#ifdef HAVE_ADS - - /* translated the ACB_CTRL Flags to UserFlags (userAccountControl) */ @@ -168,4 +166,16 @@ uint32 ads_gtype2atype(uint32 gtype) return atype; } -#endif +/* turn a sAMAccountType into a SID_NAME_USE */ +enum SID_NAME_USE ads_atype_map(uint32 atype) +{ + switch (atype & 0xF0000000) { + case ATYPE_GLOBAL_GROUP: + return SID_NAME_DOM_GRP; + case ATYPE_ACCOUNT: + return SID_NAME_USER; + default: + DEBUG(1,("hmm, need to map account type 0x%x\n", atype)); + } + return SID_NAME_UNKNOWN; +} diff --git a/source3/libads/kerberos.c b/source3/libads/kerberos.c index a80837cf4df..b586d842262 100644 --- a/source3/libads/kerberos.c +++ b/source3/libads/kerberos.c @@ -114,7 +114,14 @@ int ads_kinit_password(ADS_STRUCT *ads) char *s; int ret; - asprintf(&s, "%s@%s", ads->auth.user_name, ads->auth.realm); + if (asprintf(&s, "%s@%s", ads->auth.user_name, ads->auth.realm) == -1) { + return KRB5_CC_NOMEM; + } + + if (!ads->auth.password) { + return KRB5_LIBOS_CANTREADPWD; + } + ret = kerberos_kinit_password(s, ads->auth.password, ads->auth.time_offset); if (ret) { diff --git a/source3/libads/ldap.c b/source3/libads/ldap.c index b4c7c3970c0..e09ffeb3f6b 100644 --- a/source3/libads/ldap.c +++ b/source3/libads/ldap.c @@ -1337,9 +1337,7 @@ ADS_STATUS ads_set_machine_sd(ADS_STRUCT *ads, const char *hostname, char *dn) const char *attrs[] = {"ntSecurityDescriptor", "objectSid", 0}; char *exp = 0; size_t sd_size = 0; - struct berval **bvals = 0; struct berval bval = {0, NULL}; - prs_struct ps; prs_struct ps_wire; LDAPMessage *res = 0; @@ -1356,37 +1354,39 @@ ADS_STATUS ads_set_machine_sd(ADS_STRUCT *ads, const char *hostname, char *dn) ret = ADS_ERROR(LDAP_SUCCESS); - asprintf(&exp, "(samAccountName=%s$)", hostname); + if (asprintf(&exp, "(samAccountName=%s$)", hostname) == -1) { + DEBUG(1, ("ads_set_machine_sd: asprintf failed!\n")); + return ADS_ERROR_NT(NT_STATUS_NO_MEMORY); + } + ret = ads_search(ads, (void *) &res, exp, attrs); if (!ADS_ERR_OK(ret)) return ret; msg = ads_first_entry(ads, res); - bvals = ldap_get_values_len(ads->ld, msg, attrs[0]); ads_pull_sid(ads, msg, attrs[1], &sid); - ads_msgfree(ads, res); -#if 0 - file_save("/tmp/sec_desc.old", bvals[0]->bv_val, bvals[0]->bv_len); -#endif - if (!(ctx = talloc_init_named("sec_io_desc"))) - return ADS_ERROR(LDAP_NO_MEMORY); - - prs_init(&ps, bvals[0]->bv_len, ctx, UNMARSHALL); - prs_append_data(&ps, bvals[0]->bv_val, bvals[0]->bv_len); - ps.data_offset = 0; - ldap_value_free_len(bvals); - - if (!sec_io_desc("sd", &psd, &ps, 1)) + if (!(ctx = talloc_init_named("sec_io_desc"))) { + ret = ADS_ERROR(LDAP_NO_MEMORY); goto ads_set_sd_error; + } + + if (!ads_pull_sd(ads, ctx, msg, attrs[0], &psd)) { + ret = ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER); + goto ads_set_sd_error; + } status = sec_desc_add_sid(ctx, &psd, &sid, SEC_RIGHTS_FULL_CTRL, &sd_size); - if (!NT_STATUS_IS_OK(status)) + if (!NT_STATUS_IS_OK(status)) { + ret = ADS_ERROR_NT(status); goto ads_set_sd_error; + } prs_init(&ps_wire, sd_size, ctx, MARSHALL); - if (!sec_io_desc("sd_wire", &psd, &ps_wire, 1)) + if (!sec_io_desc("sd_wire", &psd, &ps_wire, 1)) { + ret = ADS_ERROR(LDAP_NO_MEMORY); goto ads_set_sd_error; + } #if 0 file_save("/tmp/sec_desc.new", ps_wire.data_p, sd_size); @@ -1398,16 +1398,11 @@ ADS_STATUS ads_set_machine_sd(ADS_STRUCT *ads, const char *hostname, char *dn) ads_mod_ber(ctx, &mods, attrs[0], &bval); ret = ads_gen_mod(ads, dn, mods); - prs_mem_free(&ps); +ads_set_sd_error: + ads_msgfree(ads, res); prs_mem_free(&ps_wire); talloc_destroy(ctx); return ret; - -ads_set_sd_error: - prs_mem_free(&ps); - prs_mem_free(&ps_wire); - talloc_destroy(ctx); - return ADS_ERROR(LDAP_NO_MEMORY); } /** @@ -1613,6 +1608,60 @@ int ads_pull_sids(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, return count; } +/** + * pull a SEC_DESC from a ADS result + * @param ads connection to ads server + * @param mem_ctx TALLOC_CTX for allocating sid array + * @param msg Results of search + * @param field Attribute to retrieve + * @param sd Pointer to *SEC_DESC to store result (talloc()ed) + * @return boolean inidicating success +*/ +BOOL ads_pull_sd(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, + void *msg, const char *field, SEC_DESC **sd) +{ + struct berval **values; + prs_struct ps; + BOOL ret = False; + + values = ldap_get_values_len(ads->ld, msg, field); + + if (!values) return False; + + if (values[0]) { + prs_init(&ps, values[0]->bv_len, mem_ctx, UNMARSHALL); + prs_append_data(&ps, values[0]->bv_val, values[0]->bv_len); + ps.data_offset = 0; + + ret = sec_io_desc("sd", sd, &ps, 1); + } + + ldap_value_free_len(values); + return ret; +} + +/* + * in order to support usernames longer than 21 characters we need to + * use both the sAMAccountName and the userPrincipalName attributes + * It seems that not all users have the userPrincipalName attribute set + * + * @param ads connection to ads server + * @param mem_ctx TALLOC_CTX for allocating sid array + * @param msg Results of search + * @return the username + */ +char *ads_pull_username(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, void *msg) +{ + char *ret, *p; + + ret = ads_pull_string(ads, mem_ctx, msg, "userPrincipalName"); + if (ret && (p = strchr(ret, '@'))) { + *p = 0; + return ret; + } + return ads_pull_string(ads, mem_ctx, msg, "sAMAccountName"); +} + /** * find the update serial number - this is the core of the ldap cache diff --git a/source3/libads/ldap_utils.c b/source3/libads/ldap_utils.c new file mode 100644 index 00000000000..fbc9eb433f0 --- /dev/null +++ b/source3/libads/ldap_utils.c @@ -0,0 +1,90 @@ +/* + Unix SMB/CIFS implementation. + + Some Helpful wrappers on LDAP + + Copyright (C) Andrew Tridgell 2001 + + 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. +*/ + +#include "includes.h" +/* + a wrapper around ldap_search_s that retries depending on the error code + this is supposed to catch dropped connections and auto-reconnect +*/ +ADS_STATUS ads_do_search_retry(ADS_STRUCT *ads, const char *bind_path, int scope, + const char *exp, + const char **attrs, void **res) +{ + ADS_STATUS status; + int count = 3; + char *bp; + + if (!ads->ld && + time(NULL) - ads->last_attempt < ADS_RECONNECT_TIME) { + return ADS_ERROR(LDAP_SERVER_DOWN); + } + + bp = strdup(bind_path); + + while (count--) { + status = ads_do_search_all(ads, bp, scope, exp, attrs, res); + if (ADS_ERR_OK(status)) { + DEBUG(5,("Search for %s gave %d replies\n", + exp, ads_count_replies(ads, *res))); + free(bp); + return status; + } + + if (*res) ads_msgfree(ads, *res); + *res = NULL; + DEBUG(3,("Reopening ads connection to realm '%s' after error %s\n", + ads->config.realm, ads_errstr(status))); + if (ads->ld) { + ldap_unbind(ads->ld); + } + ads->ld = NULL; + status = ads_connect(ads); + if (!ADS_ERR_OK(status)) { + DEBUG(1,("ads_search_retry: failed to reconnect (%s)\n", + ads_errstr(status))); + ads_destroy(&ads); + free(bp); + return status; + } + } + free(bp); + + DEBUG(1,("ads reopen failed after error %s\n", ads_errstr(status))); + return status; +} + + +ADS_STATUS ads_search_retry(ADS_STRUCT *ads, void **res, + const char *exp, + const char **attrs) +{ + return ads_do_search_retry(ads, ads->config.bind_path, LDAP_SCOPE_SUBTREE, + exp, attrs, res); +} + +ADS_STATUS ads_search_retry_dn(ADS_STRUCT *ads, void **res, + const char *dn, + const char **attrs) +{ + return ads_do_search_retry(ads, dn, LDAP_SCOPE_BASE, + "(objectclass=*)", attrs, res); +} diff --git a/source3/nsswitch/winbindd_ads.c b/source3/nsswitch/winbindd_ads.c index 4f91ed0f20c..228c4a2a088 100644 --- a/source3/nsswitch/winbindd_ads.c +++ b/source3/nsswitch/winbindd_ads.c @@ -31,74 +31,6 @@ static char *primary_realm; -/* - a wrapper around ldap_search_s that retries depending on the error code - this is supposed to catch dropped connections and auto-reconnect -*/ -ADS_STATUS ads_do_search_retry(ADS_STRUCT *ads, const char *bind_path, int scope, - const char *exp, - const char **attrs, void **res) -{ - ADS_STATUS status; - int count = 3; - char *bp; - - if (!ads->ld && - time(NULL) - ads->last_attempt < ADS_RECONNECT_TIME) { - return ADS_ERROR(LDAP_SERVER_DOWN); - } - - bp = strdup(bind_path); - - while (count--) { - status = ads_do_search_all(ads, bp, scope, exp, attrs, res); - if (ADS_ERR_OK(status)) { - DEBUG(5,("Search for %s gave %d replies\n", - exp, ads_count_replies(ads, *res))); - free(bp); - return status; - } - - if (*res) ads_msgfree(ads, *res); - *res = NULL; - DEBUG(3,("Reopening ads connection to realm '%s' after error %s\n", - ads->config.realm, ads_errstr(status))); - if (ads->ld) { - ldap_unbind(ads->ld); - } - ads->ld = NULL; - status = ads_connect(ads); - if (!ADS_ERR_OK(status)) { - DEBUG(1,("ads_search_retry: failed to reconnect (%s)\n", - ads_errstr(status))); - ads_destroy(&ads); - free(bp); - return status; - } - } - free(bp); - - DEBUG(1,("ads reopen failed after error %s\n", ads_errstr(status))); - return status; -} - - -ADS_STATUS ads_search_retry(ADS_STRUCT *ads, void **res, - const char *exp, - const char **attrs) -{ - return ads_do_search_retry(ads, ads->config.bind_path, LDAP_SCOPE_SUBTREE, - exp, attrs, res); -} - -ADS_STATUS ads_search_retry_dn(ADS_STRUCT *ads, void **res, - const char *dn, - const char **attrs) -{ - return ads_do_search_retry(ads, dn, LDAP_SCOPE_BASE, - "(objectclass=*)", attrs, res); -} - /* return our ads connections structure for a domain. We keep the connection open to make things faster @@ -166,37 +98,6 @@ static void sid_from_rid(struct winbindd_domain *domain, uint32 rid, DOM_SID *si sid_append_rid(sid, rid); } -/* turn a sAMAccountType into a SID_NAME_USE */ -static enum SID_NAME_USE ads_atype_map(uint32 atype) -{ - switch (atype & 0xF0000000) { - case ATYPE_GLOBAL_GROUP: - return SID_NAME_DOM_GRP; - case ATYPE_ACCOUNT: - return SID_NAME_USER; - default: - DEBUG(1,("hmm, need to map account type 0x%x\n", atype)); - } - return SID_NAME_UNKNOWN; -} - -/* - in order to support usernames longer than 21 characters we need to - use both the sAMAccountName and the userPrincipalName attributes - It seems that not all users have the userPrincipalName attribute set -*/ -static char *pull_username(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, void *msg) -{ - char *ret, *p; - - ret = ads_pull_string(ads, mem_ctx, msg, "userPrincipalName"); - if (ret && (p = strchr(ret, '@'))) { - *p = 0; - return ret; - } - return ads_pull_string(ads, mem_ctx, msg, "sAMAccountName"); -} - /* Query display info for a realm. This is the basic user list fn */ static NTSTATUS query_user_list(struct winbindd_domain *domain, @@ -254,7 +155,7 @@ static NTSTATUS query_user_list(struct winbindd_domain *domain, continue; } - name = pull_username(ads, mem_ctx, msg); + name = ads_pull_username(ads, mem_ctx, msg); gecos = ads_pull_string(ads, mem_ctx, msg, "name"); if (!ads_pull_sid(ads, msg, "objectSid", &sid)) { DEBUG(1,("No sid for %s !?\n", name)); @@ -341,7 +242,7 @@ static NTSTATUS enum_dom_groups(struct winbindd_domain *domain, &account_type) || !(account_type & ATYPE_GLOBAL_GROUP)) continue; - name = pull_username(ads, mem_ctx, msg); + name = ads_pull_username(ads, mem_ctx, msg); gecos = ads_pull_string(ads, mem_ctx, msg, "name"); if (!ads_pull_sid(ads, msg, "objectSid", &sid)) { DEBUG(1,("No sid for %s !?\n", name)); @@ -371,63 +272,21 @@ done: return status; } - /* convert a single name to a sid in a domain */ static NTSTATUS name_to_sid(struct winbindd_domain *domain, const char *name, DOM_SID *sid, enum SID_NAME_USE *type) { - ADS_STRUCT *ads = NULL; - const char *attrs[] = {"objectSid", "sAMAccountType", NULL}; - int count; - ADS_STATUS rc; - void *res = NULL; - char *exp; - uint32 t; - NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + ADS_STRUCT *ads; DEBUG(3,("ads: name_to_sid\n")); ads = ads_cached_connection(domain); - if (!ads) goto done; + if (!ads) + return NT_STATUS_UNSUCCESSFUL; - /* accept either the win2000 or the pre-win2000 username */ - asprintf(&exp, "(|(sAMAccountName=%s)(userPrincipalName=%s@%s))", - name, name, ads->config.realm); - rc = ads_search_retry(ads, &res, exp, attrs); - free(exp); - if (!ADS_ERR_OK(rc)) { - DEBUG(1,("name_to_sid ads_search: %s\n", ads_errstr(rc))); - goto done; - } - - count = ads_count_replies(ads, res); - if (count != 1) { - DEBUG(1,("name_to_sid: %s not found\n", name)); - goto done; - } - - if (!ads_pull_sid(ads, res, "objectSid", sid)) { - DEBUG(1,("No sid for %s !?\n", name)); - goto done; - } - - if (!ads_pull_uint32(ads, res, "sAMAccountType", &t)) { - DEBUG(1,("No sAMAccountType for %s !?\n", name)); - goto done; - } - - *type = ads_atype_map(t); - - status = NT_STATUS_OK; - - DEBUG(3,("ads name_to_sid mapped %s\n", name)); - -done: - if (res) ads_msgfree(ads, res); - - return status; + return ads_name_to_sid(ads, name, sid, type); } /* convert a sid to a user or group name */ @@ -438,46 +297,12 @@ static NTSTATUS sid_to_name(struct winbindd_domain *domain, enum SID_NAME_USE *type) { ADS_STRUCT *ads = NULL; - const char *attrs[] = {"userPrincipalName", - "sAMAccountName", - "sAMAccountType", NULL}; - ADS_STATUS rc; - void *msg = NULL; - char *exp; - char *sidstr; - uint32 atype; - NTSTATUS status = NT_STATUS_UNSUCCESSFUL; - DEBUG(3,("ads: sid_to_name\n")); - ads = ads_cached_connection(domain); - if (!ads) goto done; + if (!ads) + return NT_STATUS_UNSUCCESSFUL; - sidstr = sid_binstring(sid); - asprintf(&exp, "(objectSid=%s)", sidstr); - rc = ads_search_retry(ads, &msg, exp, attrs); - free(exp); - free(sidstr); - if (!ADS_ERR_OK(rc)) { - DEBUG(1,("sid_to_name ads_search: %s\n", ads_errstr(rc))); - goto done; - } - - if (!ads_pull_uint32(ads, msg, "sAMAccountType", &atype)) { - goto done; - } - - *name = pull_username(ads, mem_ctx, msg); - *type = ads_atype_map(atype); - - status = NT_STATUS_OK; - - DEBUG(3,("ads sid_to_name mapped %s\n", *name)); - -done: - if (msg) ads_msgfree(ads, msg); - - return status; + return ads_sid_to_name(ads, mem_ctx, sid, name, type); } @@ -504,7 +329,7 @@ static BOOL dn_lookup(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, goto failed; } - (*name) = pull_username(ads, mem_ctx, res); + (*name) = ads_pull_username(ads, mem_ctx, res); if (!ads_pull_uint32(ads, res, "sAMAccountType", &atype)) { goto failed; @@ -566,7 +391,7 @@ static NTSTATUS query_user(struct winbindd_domain *domain, goto done; } - info->acct_name = pull_username(ads, mem_ctx, msg); + info->acct_name = ads_pull_username(ads, mem_ctx, msg); info->full_name = ads_pull_string(ads, mem_ctx, msg, "name"); if (!ads_pull_sid(ads, msg, "objectSid", &sid)) { DEBUG(1,("No sid for %d !?\n", user_rid));