From 0af1500fc0bafe61019f1b2ab1d9e1d369221240 Mon Sep 17 00:00:00 2001 From: Gerald Carter Date: Fri, 3 Feb 2006 22:19:41 +0000 Subject: r13316: Let the carnage begin.... Sync with trunk as off r13315 (This used to be commit 17e63ac4ed8325c0d44fe62b2442449f3298559f) --- source3/nsswitch/pam_winbind.c | 991 +++++++++++++++++++++--------- source3/nsswitch/pam_winbind.h | 56 +- source3/nsswitch/wb_client.c | 16 +- source3/nsswitch/wbinfo.c | 159 ++++- source3/nsswitch/winbindd.c | 14 +- source3/nsswitch/winbindd.h | 17 +- source3/nsswitch/winbindd_ads.c | 19 +- source3/nsswitch/winbindd_cache.c | 858 +++++++++++++++++++++++--- source3/nsswitch/winbindd_cm.c | 26 +- source3/nsswitch/winbindd_cred_cache.c | 270 +++++++++ source3/nsswitch/winbindd_creds.c | 162 +++++ source3/nsswitch/winbindd_dual.c | 217 ++++++- source3/nsswitch/winbindd_group.c | 99 ++- source3/nsswitch/winbindd_misc.c | 17 + source3/nsswitch/winbindd_nss.h | 78 ++- source3/nsswitch/winbindd_pam.c | 1026 +++++++++++++++++++++++++++++++- source3/nsswitch/winbindd_passdb.c | 172 ++++-- source3/nsswitch/winbindd_reconnect.c | 32 + source3/nsswitch/winbindd_rpc.c | 69 ++- source3/nsswitch/winbindd_sid.c | 50 +- source3/nsswitch/winbindd_user.c | 8 +- source3/nsswitch/winbindd_util.c | 13 +- 22 files changed, 3833 insertions(+), 536 deletions(-) create mode 100644 source3/nsswitch/winbindd_cred_cache.c create mode 100644 source3/nsswitch/winbindd_creds.c (limited to 'source3/nsswitch') diff --git a/source3/nsswitch/pam_winbind.c b/source3/nsswitch/pam_winbind.c index 61c01daa16..57e05dc4bb 100644 --- a/source3/nsswitch/pam_winbind.c +++ b/source3/nsswitch/pam_winbind.c @@ -3,12 +3,14 @@ Copyright Andrew Tridgell 2000 Copyright Tim Potter 2000 Copyright Andrew Bartlett 2002 + Copyright Guenther Deschner 2005-2006 largely based on pam_userdb by Cristian Gafton also contains large slabs of code from pam_unix by Elliot Lee (see copyright below for full details) */ +#include "includes.h" #include "pam_winbind.h" /* data tokens */ @@ -27,41 +29,122 @@ static void _pam_log(int err, const char *format, ...) closelog(); } +static void _pam_log_debug(int ctrl, int err, const char *format, ...) +{ + va_list args; + + if (!(ctrl & WINBIND_DEBUG_ARG)) { + return; + } + + va_start(args, format); + openlog(MODULE_NAME, LOG_CONS|LOG_PID, LOG_AUTH); + vsyslog(err, format, args); + va_end(args); + closelog(); +} + static int _pam_parse(int argc, const char **argv) { - int ctrl; + int ctrl = 0; + + load_case_tables(); + + if (!lp_load(dyn_CONFIGFILE,True,False,False,True)) { + return -1; + } + + if (lp_parm_bool(-1, "pam_winbind", "cached_login", False)) { + ctrl |= WINBIND_CACHED_LOGIN; + } + if (lp_parm_bool(-1, "pam_winbind", "krb5_auth", False)) { + ctrl |= WINBIND_KRB5_AUTH; + } + if (lp_parm_const_string(-1, "pam_winbind", "krb5_ccache_type", NULL) != NULL) { + ctrl |= WINBIND_KRB5_CCACHE_TYPE; + } + if ((lp_parm_const_string(-1, "pam_winbind", "require-membership-of", NULL) != NULL) || + (lp_parm_const_string(-1, "pam_winbind", "require_membership_of", NULL) != NULL)) { + ctrl |= WINBIND_REQUIRED_MEMBERSHIP; + } + if (lp_parm_bool(-1, "pam_winbind", "create_homedir", False)) { + ctrl |= WINBIND_CREATE_HOMEDIR; + } + /* step through arguments */ - for (ctrl = 0; argc-- > 0; ++argv) { + for (; argc-- > 0; ++argv) { /* generic options */ - - if (!strcmp(*argv,"debug")) + + if (!StrCaseCmp(*argv, "debug")) ctrl |= WINBIND_DEBUG_ARG; - else if (!strcasecmp(*argv, "use_authtok")) + else if (strequal(*argv, "use_authtok")) ctrl |= WINBIND_USE_AUTHTOK_ARG; - else if (!strcasecmp(*argv, "use_first_pass")) + else if (strequal(*argv, "use_first_pass")) ctrl |= WINBIND_USE_FIRST_PASS_ARG; - else if (!strcasecmp(*argv, "try_first_pass")) + else if (strequal(*argv, "try_first_pass")) ctrl |= WINBIND_TRY_FIRST_PASS_ARG; - else if (!strcasecmp(*argv, "unknown_ok")) + else if (strequal(*argv, "unknown_ok")) ctrl |= WINBIND_UNKNOWN_OK_ARG; - else if (!strncasecmp(*argv, "require_membership_of", strlen("require_membership_of"))) + else if (strnequal(*argv, "require_membership_of", strlen("require_membership_of"))) ctrl |= WINBIND_REQUIRED_MEMBERSHIP; - else if (!strncasecmp(*argv, "require-membership-of", strlen("require-membership-of"))) + else if (strnequal(*argv, "require-membership-of", strlen("require-membership-of"))) ctrl |= WINBIND_REQUIRED_MEMBERSHIP; + else if (strequal(*argv, "krb5_auth")) + ctrl |= WINBIND_KRB5_AUTH; + else if (strnequal(*argv, "krb5_ccache_type", strlen("krb5_ccache_type"))) + ctrl |= WINBIND_KRB5_CCACHE_TYPE; + else if (strequal(*argv, "cached_login")) + ctrl |= WINBIND_CACHED_LOGIN; + else if (strequal(*argv, "create_homedir")) + ctrl |= WINBIND_CREATE_HOMEDIR; else { _pam_log(LOG_ERR, "pam_parse: unknown option; %s", *argv); } + } - return ctrl; -} +}; static void _pam_winbind_cleanup_func(pam_handle_t *pamh, void *data, int error_status) { SAFE_FREE(data); } +static const struct ntstatus_errors { + const char *ntstatus_string; + const char *error_string; +} ntstatus_errors[] = { + {"NT_STATUS_OK", "Success"}, + {"NT_STATUS_BACKUP_CONTROLLER", "No primary Domain Controler available"}, + {"NT_STATUS_PWD_TOO_SHORT", "Password too short"}, + {"NT_STATUS_PWD_TOO_RECENT", "The password of this user is too recent to change"}, + {"NT_STATUS_PWD_HISTORY_CONFLICT", "Password is already in password history"}, + {"NT_STATUS_PASSWORD_EXPIRED", "Your password has expired"}, + {"NT_STATUS_PASSWORD_MUST_CHANGE", "You need to change your password now"}, + {"NT_STATUS_INVALID_WORKSTATION", "You are not allowed to logon from this workstation"}, + {"NT_STATUS_INVALID_LOGON_HOURS", "You are not allowed to logon at this time"}, + {"NT_STATUS_ACCOUNT_EXPIRED", "Your account has expired. Please contact your System administrator"}, /* SCNR */ + {"NT_STATUS_ACCOUNT_DISABLED", "Your account is disabled. Please contact your System administrator"}, /* SCNR */ + {"NT_STATUS_ACCOUNT_LOCKED_OUT", "Your account has been locked. Please contact your System administrator"}, /* SCNR */ + {"NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT", "Invalid Trust Account"}, + {"NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT", "Invalid Trust Account"}, + {"NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT", "Invalid Trust Account"}, + {"NT_STATUS_ACCESS_DENIED", "Access is denied"}, + {NULL, NULL} +}; + +const char *_get_ntstatus_error_string(const char *nt_status_string) +{ + int i; + for (i=0; ntstatus_errors[i].ntstatus_string != NULL; i++) { + if (strequal(ntstatus_errors[i].ntstatus_string, nt_status_string)) { + return ntstatus_errors[i].error_string; + } + } + return NULL; +} + /* --- authentication management functions --- */ /* Attempt a conversation */ @@ -70,16 +153,16 @@ static int converse(pam_handle_t *pamh, int nargs, struct pam_message **message, struct pam_response **response) { - int retval; - struct pam_conv *conv; - - retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv ) ; - if (retval == PAM_SUCCESS) { - retval = conv->conv(nargs, (const struct pam_message **)message, - response, conv->appdata_ptr); - } + int retval; + struct pam_conv *conv; + + retval = pam_get_item(pamh, PAM_CONV, (const void **) &conv ); + if (retval == PAM_SUCCESS) { + retval = conv->conv(nargs, (const struct pam_message **)message, + response, conv->appdata_ptr); + } - return retval; /* propagate error status */ + return retval; /* propagate error status */ } @@ -103,11 +186,23 @@ static int _make_remark(pam_handle_t * pamh, int type, const char *text) return retval; } -static int pam_winbind_request(enum winbindd_cmd req_type, +static int _make_remark_format(pam_handle_t * pamh, int type, const char *format, ...) +{ + va_list args; + char *var; + + va_start(args, format); + vasprintf(&var, format, args); + va_end(args); + + return _make_remark(pamh, type, var); +} + +static int pam_winbind_request(pam_handle_t * pamh, int ctrl, + enum winbindd_cmd req_type, struct winbindd_request *request, struct winbindd_response *response) { - /* Fill in request and send down pipe */ init_request(request, req_type); @@ -140,19 +235,20 @@ static int pam_winbind_request(enum winbindd_cmd req_type, return PAM_SERVICE_ERR; } } - + return PAM_SUCCESS; } -static int pam_winbind_request_log(enum winbindd_cmd req_type, - struct winbindd_request *request, - struct winbindd_response *response, +static int pam_winbind_request_log(pam_handle_t * pamh, int ctrl, + enum winbindd_cmd req_type, + struct winbindd_request *request, + struct winbindd_response *response, const char *user) { int retval; - retval = pam_winbind_request(req_type, request, response); + retval = pam_winbind_request(pamh, ctrl, req_type, request, response); switch (retval) { case PAM_AUTH_ERR: @@ -168,13 +264,12 @@ static int pam_winbind_request_log(enum winbindd_cmd req_type, _pam_log(LOG_WARNING, "user `%s' password expired", user); return retval; case PAM_NEW_AUTHTOK_REQD: - /* password expired */ + /* new password required */ _pam_log(LOG_WARNING, "user `%s' new password required", user); return retval; case PAM_USER_UNKNOWN: /* the user does not exist */ - if (ctrl & WINBIND_DEBUG_ARG) - _pam_log(LOG_NOTICE, "user `%s' not found", + _pam_log_debug(ctrl, LOG_NOTICE, "user `%s' not found", user); if (ctrl & WINBIND_UNKNOWN_OK_ARG) { return PAM_IGNORE; @@ -191,6 +286,7 @@ static int pam_winbind_request_log(enum winbindd_cmd req_type, /* Otherwise, the authentication looked good */ _pam_log(LOG_NOTICE, "user '%s' OK", user); } + return retval; default: /* we don't know anything about this return value */ @@ -201,24 +297,67 @@ static int pam_winbind_request_log(enum winbindd_cmd req_type, } /* talk to winbindd */ -static int winbind_auth_request(const char *user, const char *pass, const char *member, int ctrl) +static int winbind_auth_request(pam_handle_t * pamh, + int ctrl, + const char *user, + const char *pass, + const char *member, + const char *cctype, + int process_result) { struct winbindd_request request; struct winbindd_response response; + int ret; ZERO_STRUCT(request); + ZERO_STRUCT(response); strncpy(request.data.auth.user, user, - sizeof(request.data.auth.user)-1); + sizeof(request.data.auth.user)-1); strncpy(request.data.auth.pass, pass, - sizeof(request.data.auth.pass)-1); + sizeof(request.data.auth.pass)-1); + + request.data.auth.krb5_cc_type[0] = '\0'; + request.data.auth.uid = -1; + + request.flags = WBFLAG_PAM_INFO3_TEXT | WBFLAG_PAM_CONTACT_TRUSTDOM; + + if (ctrl & WINBIND_KRB5_AUTH) { + + struct passwd *pwd = NULL; + + _pam_log_debug(ctrl, LOG_DEBUG, "enabling krb5 login flag\n"); + + request.flags |= WBFLAG_PAM_KRB5 | WBFLAG_PAM_FALLBACK_AFTER_KRB5; + + pwd = getpwnam(user); + if (pwd == NULL) { + return PAM_USER_UNKNOWN; + } + request.data.auth.uid = pwd->pw_uid; + } - if (member == NULL ) - return pam_winbind_request_log(WINBINDD_PAM_AUTH, &request, &response, ctrl, user); + if (ctrl & WINBIND_CACHED_LOGIN) { + _pam_log_debug(ctrl, LOG_DEBUG, "enabling cached login flag\n"); + request.flags |= WBFLAG_PAM_CACHED_LOGIN; + } + + if (cctype != NULL) { + strncpy(request.data.auth.krb5_cc_type, cctype, + sizeof(request.data.auth.krb5_cc_type) - 1); + _pam_log_debug(ctrl, LOG_DEBUG, "enabling request for a %s krb5 ccache\n", cctype); + } + + request.data.auth.require_membership_of_sid[0] = '\0'; + + if (member != NULL) { + strncpy(request.data.auth.require_membership_of_sid, member, + sizeof(request.data.auth.require_membership_of_sid)-1); + } /* lookup name? */ - if (!strncmp("S-", member, 2) == 0) { + if ( (member != NULL) && (strncmp("S-", member, 2) != 0) ) { struct winbindd_request sid_request; struct winbindd_response sid_response; @@ -226,55 +365,195 @@ static int winbind_auth_request(const char *user, const char *pass, const char * ZERO_STRUCT(sid_request); ZERO_STRUCT(sid_response); - if (ctrl & WINBIND_DEBUG_ARG) - _pam_log(LOG_DEBUG, "no sid given, looking up: %s\n", member); + _pam_log_debug(ctrl, LOG_DEBUG, "no sid given, looking up: %s\n", member); /* fortunatly winbindd can handle non-separated names */ - strcpy(sid_request.data.name.name, member); + fstrcpy(sid_request.data.name.name, member); - if (pam_winbind_request_log(WINBINDD_LOOKUPNAME, &sid_request, &sid_response, ctrl, user)) { + if (pam_winbind_request_log(pamh, ctrl, WINBINDD_LOOKUPNAME, &sid_request, &sid_response, user)) { _pam_log(LOG_INFO, "could not lookup name: %s\n", member); return PAM_AUTH_ERR; } member = sid_response.data.sid.sid; + + strncpy(request.data.auth.require_membership_of_sid, member, + sizeof(request.data.auth.require_membership_of_sid)-1); } + + ret = pam_winbind_request_log(pamh, ctrl, WINBINDD_PAM_AUTH, &request, &response, user); - strncpy(request.data.auth.require_membership_of_sid, member, - sizeof(request.data.auth.require_membership_of_sid)-1); + if ((ctrl & WINBIND_KRB5_AUTH) && + response.data.auth.krb5ccname[0] != '\0') { + + char var[PATH_MAX]; + + _pam_log_debug(ctrl, LOG_DEBUG, "request returned KRB5CCNAME: %s", + response.data.auth.krb5ccname); - return pam_winbind_request_log(WINBINDD_PAM_AUTH, &request, &response, ctrl, user); + snprintf(var, sizeof(var), "KRB5CCNAME=%s", response.data.auth.krb5ccname); + + ret = pam_putenv(pamh, var); + if (ret != PAM_SUCCESS) { + _pam_log(LOG_ERR, "failed to set KRB5CCNAME to %s", var); + return ret; + } + } + + if (!process_result) { + return ret; + } + + if (ret) { + PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, response, "NT_STATUS_PASSWORD_EXPIRED"); + PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, response, "NT_STATUS_PASSWORD_MUST_CHANGE"); + PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, response, "NT_STATUS_INVALID_WORKSTATION"); + PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, response, "NT_STATUS_INVALID_LOGON_HOURS"); + PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, response, "NT_STATUS_ACCOUNT_EXPIRED"); + PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, response, "NT_STATUS_ACCOUNT_DISABLED"); + PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, response, "NT_STATUS_ACCOUNT_LOCKED_OUT"); + PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, response, "NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT"); + PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, response, "NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT"); + PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, response, "NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT"); + } + + /* handle the case where the auth was ok, but the password must expire right now */ + /* good catch from Ralf Haferkamp: an expiry of "never" is translated to -1 */ + if ((response.data.auth.policy.expire > 0) && + (response.data.auth.info3.pass_last_set_time + response.data.auth.policy.expire < time(NULL))) { + + ret = PAM_AUTHTOK_EXPIRED; + + _pam_log_debug(ctrl, LOG_DEBUG,"Password has expired (Password was last set: %d, " + "the policy says it should expire here %d (now it's: %d)\n", + response.data.auth.info3.pass_last_set_time, + response.data.auth.info3.pass_last_set_time + response.data.auth.policy.expire, + time(NULL)); + + PAM_WB_REMARK_DIRECT_RET(pamh, "NT_STATUS_PASSWORD_EXPIRED"); + + } + + /* warn a user if the password is about to expire soon */ + if ((response.data.auth.policy.expire) && + (response.data.auth.info3.pass_last_set_time + response.data.auth.policy.expire > time(NULL) ) ) { + + int days = response.data.auth.policy.expire / SECONDS_PER_DAY; + if (days <= DAYS_TO_WARN_BEFORE_PWD_EXPIRES) { + _make_remark_format(pamh, PAM_TEXT_INFO, "Your password will expire in %d days", days); + } + } + + if (response.data.auth.info3.user_flgs & LOGON_CACHED_ACCOUNT) { + _make_remark(pamh, PAM_ERROR_MSG, "Logging on using cached account. Network ressources can be unavailable"); + } + + /* save the CIFS homedir for pam_cifs / pam_mount */ + if (response.data.auth.info3.home_dir[0] != '\0') { + char *buf; + + if (!asprintf(&buf, "%s", response.data.auth.info3.home_dir)) { + return PAM_BUF_ERR; + } + + pam_set_data( pamh, PAM_WINBIND_HOMEDIR, (void *)buf, _pam_winbind_cleanup_func); + } + + return ret; } /* talk to winbindd */ -static int winbind_chauthtok_request(const char *user, const char *oldpass, - const char *newpass, int ctrl) +static int winbind_chauthtok_request(pam_handle_t * pamh, + int ctrl, + const char *user, + const char *oldpass, + const char *newpass) { struct winbindd_request request; struct winbindd_response response; + int ret; ZERO_STRUCT(request); + ZERO_STRUCT(response); - if (request.data.chauthtok.user == NULL) return -2; + if (request.data.chauthtok.user == NULL) return -2; strncpy(request.data.chauthtok.user, user, - sizeof(request.data.chauthtok.user) - 1); - - if (oldpass != NULL) { - strncpy(request.data.chauthtok.oldpass, oldpass, - sizeof(request.data.chauthtok.oldpass) - 1); - } else { - request.data.chauthtok.oldpass[0] = '\0'; - } - - if (newpass != NULL) { - strncpy(request.data.chauthtok.newpass, newpass, - sizeof(request.data.chauthtok.newpass) - 1); - } else { - request.data.chauthtok.newpass[0] = '\0'; - } + sizeof(request.data.chauthtok.user) - 1); + + if (oldpass != NULL) { + strncpy(request.data.chauthtok.oldpass, oldpass, + sizeof(request.data.chauthtok.oldpass) - 1); + } else { + request.data.chauthtok.oldpass[0] = '\0'; + } - return pam_winbind_request_log(WINBINDD_PAM_CHAUTHTOK, &request, &response, ctrl, user); + if (newpass != NULL) { + strncpy(request.data.chauthtok.newpass, newpass, + sizeof(request.data.chauthtok.newpass) - 1); + } else { + request.data.chauthtok.newpass[0] = '\0'; + } + + if (ctrl & WINBIND_KRB5_AUTH) { + request.flags = WBFLAG_PAM_KRB5 | WBFLAG_PAM_CONTACT_TRUSTDOM; + } + + ret = pam_winbind_request_log(pamh, ctrl, WINBINDD_PAM_CHAUTHTOK, &request, &response, user); + + if (ret == PAM_SUCCESS) { + return ret; + } + + PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, response, "NT_STATUS_BACKUP_CONTROLLER"); + PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, response, "NT_STATUS_ACCESS_DENIED"); + + /* TODO: tell the min pwd length ? */ + PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, response, "NT_STATUS_PWD_TOO_SHORT"); + + /* TODO: tell the minage ? */ + PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, response, "NT_STATUS_PWD_TOO_RECENT"); + + /* TODO: tell the history length ? */ + PAM_WB_REMARK_CHECK_RESPONSE_RET(pamh, response, "NT_STATUS_PWD_HISTORY_CONFLICT"); + + if (strequal(response.data.auth.nt_status_string, "NT_STATUS_PASSWORD_RESTRICTION")) { + + /* FIXME: avoid to send multiple PAM messages after another */ + switch (response.data.auth.reject_reason) { + case 0: + break; + case REJECT_REASON_TOO_SHORT: + PAM_WB_REMARK_DIRECT(pamh, "NT_STATUS_PWD_TOO_SHORT"); + break; + case REJECT_REASON_IN_HISTORY: + PAM_WB_REMARK_DIRECT(pamh, "NT_STATUS_PWD_HISTORY_CONFLICT"); + break; + case REJECT_REASON_NOT_COMPLEX: + _make_remark(pamh, PAM_ERROR_MSG, "Password does not meet complexity requirements"); + break; + default: + _pam_log_debug(ctrl, LOG_DEBUG, + "unknown password change reject reason: %d", + response.data.auth.reject_reason); + break; + } + + _make_remark_format(pamh, PAM_ERROR_MSG, + "Your password must be at least %d characters; " + "cannot repeat any of the your previous %d passwords" + "%s. " + "Please type a different password. " + "Type a password which meets these requirements in both text boxes.", + response.data.auth.policy.min_length_password, + response.data.auth.policy.password_history, + (response.data.auth.policy.password_properties & DOMAIN_PASSWORD_COMPLEX) ? + "; must contain capitals, numerals or punctuation; and cannot contain your account or full name" : + ""); + + } + + return ret; } /* @@ -293,21 +572,21 @@ static int valid_user(const char *user) static char *_pam_delete(register char *xx) { - _pam_overwrite(xx); - _pam_drop(xx); - return NULL; + _pam_overwrite(xx); + _pam_drop(xx); + return NULL; } /* * obtain a password from the user */ -static int _winbind_read_password(pam_handle_t * pamh - ,unsigned int ctrl - ,const char *comment - ,const char *prompt1 - ,const char *prompt2 - ,const char **pass) +static int _winbind_read_password(pam_handle_t * pamh, + unsigned int ctrl, + const char *comment, + const char *prompt1, + const char *prompt2, + const char **pass) { int authtok_flag; int retval; @@ -391,16 +670,15 @@ static int _winbind_read_password(pam_handle_t * pamh if (retval == PAM_SUCCESS) { /* a good conversation */ - token = x_strdup(resp[i - replies].resp); + token = SMB_STRDUP(resp[i - replies].resp); if (token != NULL) { if (replies == 2) { - /* verify that password entered correctly */ if (!resp[i - 1].resp - || strcmp(token, resp[i - 1].resp)) { + || StrCaseCmp(token, resp[i - 1].resp)) { _pam_delete(token); /* mistyped */ retval = PAM_AUTHTOK_RECOVER_ERR; - _make_remark(pamh ,PAM_ERROR_MSG, MISTYPED_PASS); + _make_remark(pamh, PAM_ERROR_MSG, MISTYPED_PASS); } } } else { @@ -423,8 +701,7 @@ static int _winbind_read_password(pam_handle_t * pamh } if (retval != PAM_SUCCESS) { - if (on(WINBIND_DEBUG_ARG, ctrl)) - _pam_log(LOG_DEBUG, + _pam_log_debug(ctrl, LOG_DEBUG, "unable to obtain a password"); return retval; } @@ -434,10 +711,8 @@ static int _winbind_read_password(pam_handle_t * pamh retval = pam_set_item(pamh, authtok_flag, token); _pam_delete(token); /* clean it up */ - if (retval != PAM_SUCCESS - || (retval = pam_get_item(pamh, authtok_flag - ,(const void **) &item)) - != PAM_SUCCESS) { + if (retval != PAM_SUCCESS || + (retval = pam_get_item(pamh, authtok_flag, (const void **) &item)) != PAM_SUCCESS) { _pam_log(LOG_CRIT, "error manipulating password"); return retval; @@ -450,92 +725,145 @@ static int _winbind_read_password(pam_handle_t * pamh return PAM_SUCCESS; } +const char *get_conf_item_string(int argc, + const char **argv, + int ctrl, + const char *item, + int flag) +{ + int i = 0; + char *parm = NULL; + const char *parm_opt = NULL; + + if (!(ctrl & flag)) { + goto out; + } + + /* let the pam opt take precedence over the smb.conf option */ + parm_opt = lp_parm_const_string(-1, "pam_winbind", item, NULL); + + for ( i=0; ipw_dir); + + if (directory_exist(pwd->pw_dir, &sbuf)) { + return PAM_SUCCESS; + } + + fstrcpy(create_dir, "/"); + while (next_token((const char **)&pwd->pw_dir, tok, "/", sizeof(tok))) { + + mode_t mode = 0755; + + fstrcat(create_dir, tok); + fstrcat(create_dir, "/"); + + if (!directory_exist(create_dir, &sbuf)) { + if (mkdir(create_dir, mode) != 0) { + _pam_log(LOG_ERR, "could not create dir: %s (%s)", + create_dir, strerror(errno)); + return PAM_SERVICE_ERR; + } + } + } + + if (sys_chown(create_dir, pwd->pw_uid, pwd->pw_gid) != 0) { + _pam_log(LOG_ERR, "failed to chown user homedir: %s (%s)", + create_dir, strerror(errno)); + return PAM_SERVICE_ERR; + } + } + + return PAM_SUCCESS; } + PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, - int argc, const char **argv) + int argc, const char **argv) { - /* parse arguments */ - int ctrl = _pam_parse(argc, argv); - if (ctrl & WINBIND_DEBUG_ARG) - _pam_log(LOG_DEBUG,"libpam_winbind:pam_sm_close_session handler"); - return PAM_SUCCESS; + /* parse arguments */ + int ctrl = _pam_parse(argc, argv); + if (ctrl == -1) { + return PAM_SYSTEM_ERR; + } + + _pam_log_debug(ctrl, LOG_DEBUG,"pam_winbind: pam_sm_close_session handler"); + + if (!(flags & PAM_DELETE_CRED)) { + return PAM_SUCCESS; + } + + if (ctrl & WINBIND_KRB5_AUTH) { + + /* destroy the ccache here */ + struct winbindd_request request; + struct winbindd_response response; + const char *user; + const char *ccname = NULL; + struct passwd *pwd = NULL; + + int retval; + + ZERO_STRUCT(request); + ZERO_STRUCT(response); + + retval = pam_get_user(pamh, &user, "Username: "); + if (retval == PAM_SUCCESS) { + if (user == NULL) { + _pam_log(LOG_ERR, "username was NULL!"); + return PAM_USER_UNKNOWN; + } + if (retval == PAM_SUCCESS) { + _pam_log_debug(ctrl, LOG_DEBUG, "username [%s] obtained", user); + } + } else { + _pam_log_debug(ctrl, LOG_DEBUG, "could not identify user"); + return retval; + } + + ccname = pam_getenv(pamh, "KRB5CCNAME"); + if (ccname == NULL) { + _pam_log_debug(ctrl, LOG_DEBUG, "user has no KRB5CCNAME environment"); + return PAM_BUF_ERR; + } + + fstrcpy(request.data.logoff.user, user); + fstrcpy(request.data.logoff.krb5ccname, ccname); + + pwd = getpwnam(user); + if (pwd == NULL) { + return PAM_USER_UNKNOWN; + } + request.data.logoff.uid = pwd->pw_uid; + + request.flags = WBFLAG_PAM_KRB5 | WBFLAG_PAM_CONTACT_TRUSTDOM; + + return pam_winbind_request_log(pamh, ctrl, WINBINDD_PAM_LOGOFF, &request, &response, user); + } + + return PAM_SUCCESS; } -PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags, - int argc, const char **argv) +PAM_EXTERN +int pam_sm_chauthtok(pam_handle_t * pamh, int flags, + int argc, const char **argv) { unsigned int lctrl; int retval; - unsigned int ctrl = _pam_parse(argc, argv); + unsigned int ctrl; /* */ const char *user; - const char *member = NULL; char *pass_old, *pass_new; /* */ - char *Announce; + fstring Announce; int retry = 0; + ctrl = _pam_parse(argc, argv); + if (ctrl == -1) { + return PAM_SYSTEM_ERR; + } + + _pam_log_debug(ctrl, LOG_DEBUG,"pam_winbind: pam_sm_chauthtok"); + /* * First get the name of a user */ @@ -659,13 +1111,13 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags, _pam_log(LOG_ERR, "username was NULL!"); return PAM_USER_UNKNOWN; } - if (retval == PAM_SUCCESS && on(WINBIND_DEBUG_ARG, ctrl)) - _pam_log(LOG_DEBUG, "username [%s] obtained", + if (retval == PAM_SUCCESS) { + _pam_log_debug(ctrl, LOG_DEBUG, "username [%s] obtained", user); + } } else { - if (on(WINBIND_DEBUG_ARG, ctrl)) - _pam_log(LOG_DEBUG, - "password - could not identify user"); + _pam_log_debug(ctrl, LOG_DEBUG, + "password - could not identify user"); return retval; } @@ -678,33 +1130,24 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags, /* instruct user what is happening */ #define greeting "Changing password for " - Announce = (char *) malloc(sizeof(greeting) + strlen(user)); - if (Announce == NULL) { - _pam_log(LOG_CRIT, - "password - out of memory"); - return PAM_BUF_ERR; - } - (void) strcpy(Announce, greeting); - (void) strcpy(Announce + sizeof(greeting) - 1, user); + fstrcpy(Announce, greeting); + fstrcat(Announce, user); #undef greeting lctrl = ctrl | WINBIND__OLD_PASSWORD; - retval = _winbind_read_password(pamh, lctrl - ,Announce - ,"(current) NT password: " - ,NULL - ,(const char **) &pass_old); - free(Announce); - + retval = _winbind_read_password(pamh, lctrl, + Announce, + "(current) NT password: ", + NULL, + (const char **) &pass_old); if (retval != PAM_SUCCESS) { - _pam_log(LOG_NOTICE - ,"password - (old) token not obtained"); + _pam_log(LOG_NOTICE, "password - (old) token not obtained"); return retval; } /* verify that this is the password for this user */ - retval = winbind_auth_request(user, pass_old, member, ctrl); - + retval = winbind_auth_request(pamh, ctrl, user, pass_old, NULL, NULL, False); + if (retval != PAM_ACCT_EXPIRED && retval != PAM_AUTHTOK_EXPIRED && retval != PAM_NEW_AUTHTOK_REQD @@ -716,8 +1159,7 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags, retval = pam_set_item(pamh, PAM_OLDAUTHTOK, (const void *) pass_old); pass_old = NULL; if (retval != PAM_SUCCESS) { - _pam_log(LOG_CRIT, - "failed to set PAM_OLDAUTHTOK"); + _pam_log(LOG_CRIT, "failed to set PAM_OLDAUTHTOK"); } } else if (flags & PAM_UPDATE_AUTHTOK) { @@ -729,8 +1171,8 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags, * get the old token back. */ - retval = pam_get_item(pamh, PAM_OLDAUTHTOK - ,(const void **) &pass_old); + retval = pam_get_item(pamh, PAM_OLDAUTHTOK, + (const void **) &pass_old); if (retval != PAM_SUCCESS) { _pam_log(LOG_NOTICE, "user not authenticated"); @@ -750,17 +1192,15 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags, * password -- needed for pluggable password strength checking */ - retval = _winbind_read_password(pamh, lctrl - ,NULL - ,"Enter new NT password: " - ,"Retype new NT password: " - ,(const char **) &pass_new); + retval = _winbind_read_password(pamh, lctrl, + NULL, + "Enter new NT password: ", + "Retype new NT password: ", + (const char **) &pass_new); if (retval != PAM_SUCCESS) { - if (on(WINBIND_DEBUG_ARG, ctrl)) { - _pam_log(LOG_ALERT - ,"password - new password not obtained"); - } + _pam_log_debug(ctrl, LOG_ALERT + ,"password - new password not obtained"); pass_old = NULL;/* tidy up */ return retval; } @@ -781,14 +1221,30 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags, * rebuild the password database file. */ - retval = winbind_chauthtok_request(user, pass_old, pass_new, ctrl); - _pam_overwrite(pass_new); - _pam_overwrite(pass_old); - pass_old = pass_new = NULL; + retval = winbind_chauthtok_request(pamh, ctrl, user, pass_old, pass_new); + if (retval) { + _pam_overwrite(pass_new); + _pam_overwrite(pass_old); + pass_old = pass_new = NULL; + return retval; + } + + /* just in case we need krb5 creds after a password change over msrpc */ + + if (ctrl & WINBIND_KRB5_AUTH) { + + const char *member = get_member_from_config(argc, argv, ctrl); + const char *cctype = get_krb5_cc_type_from_config(argc, argv, ctrl); + + retval = winbind_auth_request(pamh, ctrl, user, pass_new, member, cctype, False); + _pam_overwrite(pass_new); + _pam_overwrite(pass_old); + pass_old = pass_new = NULL; + } } else { retval = PAM_SERVICE_ERR; } - + return retval; } @@ -797,13 +1253,13 @@ PAM_EXTERN int pam_sm_chauthtok(pam_handle_t * pamh, int flags, /* static module data */ struct pam_module _pam_winbind_modstruct = { - MODULE_NAME, - pam_sm_authenticate, - pam_sm_setcred, - pam_sm_acct_mgmt, - pam_sm_open_session, - pam_sm_close_session, - pam_sm_chauthtok + MODULE_NAME, + pam_sm_authenticate, + pam_sm_setcred, + pam_sm_acct_mgmt, + pam_sm_open_session, + pam_sm_close_session, + pam_sm_chauthtok }; #endif @@ -812,6 +1268,7 @@ struct pam_module _pam_winbind_modstruct = { * Copyright (c) Andrew Tridgell 2000 * Copyright (c) Tim Potter 2000 * Copyright (c) Andrew Bartlettt 2002 + * Copyright (c) Guenther Deschner 2005-2006 * Copyright (c) Jan Rêkorajski 1999. * Copyright (c) Andrew G. Morgan 1996-8. * Copyright (c) Alex O. Yuriev, 1996. diff --git a/source3/nsswitch/pam_winbind.h b/source3/nsswitch/pam_winbind.h index 86ba977287..1e38269e0e 100644 --- a/source3/nsswitch/pam_winbind.h +++ b/source3/nsswitch/pam_winbind.h @@ -17,6 +17,7 @@ #include #include #include +#include #include @@ -83,8 +84,10 @@ do { \ #define WINBIND_USE_FIRST_PASS_ARG (1<<4) #define WINBIND__OLD_PASSWORD (1<<5) #define WINBIND_REQUIRED_MEMBERSHIP (1<<6) - -#define PAM_WINBIND_NEW_AUTHTOK_REQD "PAM_WINBIND_NEW_AUTHTOK_REQD" +#define WINBIND_KRB5_AUTH (1<<7) +#define WINBIND_KRB5_CCACHE_TYPE (1<<8) +#define WINBIND_CACHED_LOGIN (1<<9) +#define WINBIND_CREATE_HOMEDIR (1<<10) /* * here is the string to inform the user that the new passwords they @@ -96,4 +99,53 @@ do { \ #define on(x, y) (x & y) #define off(x, y) (!(x & y)) +#define PAM_WINBIND_NEW_AUTHTOK_REQD "PAM_WINBIND_NEW_AUTHTOK_REQD" +#define PAM_WINBIND_HOMEDIR "PAM_WINBIND_HOMEDIR" + +#define SECONDS_PER_DAY 86400 + +#define DAYS_TO_WARN_BEFORE_PWD_EXPIRES 5 + #include "winbind_client.h" + +#define PAM_WB_REMARK_DIRECT(h,x)\ +{\ + const char *error_string = NULL; \ + error_string = _get_ntstatus_error_string(x);\ + if (error_string != NULL) {\ + _make_remark(h, PAM_ERROR_MSG, error_string);\ + } else {\ + _make_remark(h, PAM_ERROR_MSG, x);\ + };\ +}; + +#define PAM_WB_REMARK_DIRECT_RET(h,x)\ +{\ + const char *error_string = NULL; \ + error_string = _get_ntstatus_error_string(x);\ + if (error_string != NULL) {\ + _make_remark(h, PAM_ERROR_MSG, error_string);\ + return ret;\ + };\ + _make_remark(h, PAM_ERROR_MSG, x);\ + return ret;\ +}; + +#define PAM_WB_REMARK_CHECK_RESPONSE_RET(h,x,y)\ +{\ + const char *ntstatus = x.data.auth.nt_status_string; \ + const char *error_string = NULL; \ + if (strequal(ntstatus,y)) {\ + error_string = _get_ntstatus_error_string(y);\ + if (error_string != NULL) {\ + _make_remark(h, PAM_ERROR_MSG, error_string);\ + return ret;\ + };\ + if (x.data.auth.error_string[0] != '\0') {\ + _make_remark(h, PAM_ERROR_MSG, x.data.auth.error_string);\ + return ret;\ + };\ + _make_remark(h, PAM_ERROR_MSG, y);\ + return ret;\ + };\ +}; diff --git a/source3/nsswitch/wb_client.c b/source3/nsswitch/wb_client.c index fcab76b033..ff0f15a122 100644 --- a/source3/nsswitch/wb_client.c +++ b/source3/nsswitch/wb_client.c @@ -247,7 +247,7 @@ BOOL winbind_gid_to_sid(DOM_SID *sid, gid_t gid) return (result == NSS_STATUS_SUCCESS); } -BOOL winbind_allocate_rid(uint32 *rid) +BOOL winbind_allocate_uid(uid_t *uid) { struct winbindd_request request; struct winbindd_response response; @@ -260,18 +260,19 @@ BOOL winbind_allocate_rid(uint32 *rid) /* Make request */ - result = winbindd_request_response(WINBINDD_ALLOCATE_RID, &request, &response); + result = winbindd_request_response(WINBINDD_ALLOCATE_UID, + &request, &response); if (result != NSS_STATUS_SUCCESS) return False; /* Copy out result */ - *rid = response.data.rid; + *uid = response.data.uid; return True; } -BOOL winbind_allocate_rid_and_gid(uint32 *rid, gid_t *gid) +BOOL winbind_allocate_gid(gid_t *gid) { struct winbindd_request request; struct winbindd_response response; @@ -284,15 +285,14 @@ BOOL winbind_allocate_rid_and_gid(uint32 *rid, gid_t *gid) /* Make request */ - result = winbindd_request_response(WINBINDD_ALLOCATE_RID_AND_GID, &request, - &response); + result = winbindd_request_response(WINBINDD_ALLOCATE_GID, + &request, &response); if (result != NSS_STATUS_SUCCESS) return False; /* Copy out result */ - *rid = response.data.rid_and_gid.rid; - *gid = response.data.rid_and_gid.gid; + *gid = response.data.gid; return True; } diff --git a/source3/nsswitch/wbinfo.c b/source3/nsswitch/wbinfo.c index 793a05f179..571a1b3e19 100644 --- a/source3/nsswitch/wbinfo.c +++ b/source3/nsswitch/wbinfo.c @@ -199,7 +199,7 @@ static BOOL wbinfo_get_userdomgroups(const char *user_sid) return False; if (response.data.num_entries != 0) - d_printf("%s", (char *)response.extra_data); + printf("%s", (char *)response.extra_data); SAFE_FREE(response.extra_data); @@ -260,15 +260,19 @@ static BOOL wbinfo_wins_byip(char *ip) /* List trusted domains */ -static BOOL wbinfo_list_domains(void) +static BOOL wbinfo_list_domains(BOOL list_all_domains) { + struct winbindd_request request; struct winbindd_response response; + ZERO_STRUCT(request); ZERO_STRUCT(response); /* Send request */ - if (winbindd_request_response(WINBINDD_LIST_TRUSTDOM, NULL, &response) != + request.data.list_all_domains = list_all_domains; + + if (winbindd_request_response(WINBINDD_LIST_TRUSTDOM, &request, &response) != NSS_STATUS_SUCCESS) return False; @@ -510,14 +514,26 @@ static BOOL wbinfo_sid_to_gid(char *sid) return True; } -static BOOL wbinfo_allocate_rid(void) +static BOOL wbinfo_allocate_uid(void) { - uint32 rid; + uid_t uid; - if (!winbind_allocate_rid(&rid)) + if (!winbind_allocate_uid(&uid)) return False; - d_printf("New rid: %d\n", rid); + d_printf("New uid: %d\n", uid); + + return True; +} + +static BOOL wbinfo_allocate_gid(void) +{ + gid_t gid; + + if (!winbind_allocate_gid(&gid)) + return False; + + d_printf("New gid: %d\n", gid); return True; } @@ -577,6 +593,67 @@ static BOOL wbinfo_lookupname(char *name) /* Authenticate a user with a plaintext password */ +static BOOL wbinfo_auth_krb5(char *username, const char *cctype, uint32 flags) +{ + struct winbindd_request request; + struct winbindd_response response; + NSS_STATUS result; + char *p; + + /* Send off request */ + + ZERO_STRUCT(request); + ZERO_STRUCT(response); + + p = strchr(username, '%'); + + if (p) { + *p = 0; + fstrcpy(request.data.auth.user, username); + fstrcpy(request.data.auth.pass, p + 1); + *p = '%'; + } else + fstrcpy(request.data.auth.user, username); + + request.flags = flags; + + fstrcpy(request.data.auth.krb5_cc_type, cctype); + + request.data.auth.uid = geteuid(); + + result = winbindd_request_response(WINBINDD_PAM_AUTH, &request, &response); + + /* Display response */ + + d_printf("plaintext kerberos password authentication for [%s] %s (requesting cctype: %s)\n", + username, (result == NSS_STATUS_SUCCESS) ? "succeeded" : "failed", cctype); + + if (response.data.auth.nt_status) + d_fprintf(stderr, "error code was %s (0x%x)\nerror messsage was: %s\n", + response.data.auth.nt_status_string, + response.data.auth.nt_status, + response.data.auth.error_string); + + if (result == NSS_STATUS_SUCCESS) { + + if (request.flags & WBFLAG_PAM_INFO3_TEXT) { + if (response.data.auth.info3.user_flgs & LOGON_CACHED_ACCOUNT) { + d_printf("user_flgs: LOGON_CACHED_ACCOUNT\n"); + } + } + + if (response.data.auth.krb5ccname[0] != '\0') { + d_printf("credentials were put in: %s\n", response.data.auth.krb5ccname); + } else { + d_printf("no credentials cached\n"); + } + } + + return result == NSS_STATUS_SUCCESS; +} + +/* Authenticate a user with a plaintext password */ + static BOOL wbinfo_auth(char *username) { struct winbindd_request request; @@ -968,7 +1045,10 @@ enum { OPT_GETDCNAME, OPT_USERDOMGROUPS, OPT_USERSIDS, - OPT_SEPARATOR + OPT_ALLOCATE_UID, + OPT_ALLOCATE_GID, + OPT_SEPARATOR, + OPT_LIST_ALL_DOMAINS }; int main(int argc, char **argv) @@ -997,9 +1077,13 @@ int main(int argc, char **argv) { "gid-to-sid", 'G', POPT_ARG_INT, &int_arg, 'G', "Converts gid to sid", "GID" }, { "sid-to-uid", 'S', POPT_ARG_STRING, &string_arg, 'S', "Converts sid to uid", "SID" }, { "sid-to-gid", 'Y', POPT_ARG_STRING, &string_arg, 'Y', "Converts sid to gid", "SID" }, - { "allocate-rid", 'A', POPT_ARG_NONE, 0, 'A', "Get a new RID out of idmap" }, + { "allocate-uid", 0, POPT_ARG_NONE, 0, OPT_ALLOCATE_UID, + "Get a new UID out of idmap" }, + { "allocate-gid", 0, POPT_ARG_NONE, 0, OPT_ALLOCATE_GID, + "Get a new GID out of idmap" }, { "check-secret", 't', POPT_ARG_NONE, 0, 't', "Check shared secret" }, { "trusted-domains", 'm', POPT_ARG_NONE, 0, 'm', "List trusted domains" }, + { "all-domains", 0, POPT_ARG_NONE, 0, OPT_LIST_ALL_DOMAINS, "List all domains (trusted and own domain)" }, { "sequence", 0, POPT_ARG_NONE, 0, OPT_SEQUENCE, "Show sequence numbers of all domains" }, { "domain-info", 'D', POPT_ARG_STRING, &string_arg, 'D', "Show most of the info we have about the domain" }, { "user-groups", 'r', POPT_ARG_STRING, &string_arg, 'r', "Get user groups", "USER" }, @@ -1015,6 +1099,11 @@ int main(int argc, char **argv) { "domain", 0, POPT_ARG_STRING, &opt_domain_name, OPT_DOMAIN_NAME, "Define to the domain to restrict operation", "domain" }, #ifdef WITH_FAKE_KASERVER { "klog", 'k', POPT_ARG_STRING, &string_arg, 'k', "set an AFS token from winbind", "user%password" }, +#endif +#ifdef HAVE_KRB5 + { "krb5auth", 'K', POPT_ARG_STRING, &string_arg, 'K', "authenticate user using Kerberos", "user%password" }, + /* destroys wbinfo --help output */ + /* "user%password,DOM\\user%password,user@EXAMPLE.COM,EXAMPLE.COM\\user%password" }, */ #endif { "separator", 0, POPT_ARG_NONE, 0, OPT_SEPARATOR, "Get the active winbind separator", NULL }, POPT_COMMON_VERSION @@ -1120,9 +1209,15 @@ int main(int argc, char **argv) goto done; } break; - case 'A': - if (!wbinfo_allocate_rid()) { - d_fprintf(stderr, "Could not allocate a RID\n"); + case OPT_ALLOCATE_UID: + if (!wbinfo_allocate_uid()) { + d_fprintf(stderr, "Could not allocate a uid\n"); + goto done; + } + break; + case OPT_ALLOCATE_GID: + if (!wbinfo_allocate_gid()) { + d_fprintf(stderr, "Could not allocate a gid\n"); goto done; } break; @@ -1133,7 +1228,7 @@ int main(int argc, char **argv) } break; case 'm': - if (!wbinfo_list_domains()) { + if (!wbinfo_list_domains(False)) { d_fprintf(stderr, "Could not list trusted domains\n"); goto done; } @@ -1190,6 +1285,38 @@ int main(int argc, char **argv) goto done; break; } + case 'K': { + BOOL got_error = False; + uint32 flags = WBFLAG_PAM_KRB5 | + WBFLAG_PAM_CACHED_LOGIN | + WBFLAG_PAM_FALLBACK_AFTER_KRB5 | + WBFLAG_PAM_INFO3_TEXT; + fstring tok; + int i; + const char *arg[] = { string_arg, NULL }; + const char *cctypes[] = { "FILE", + "KCM", + "KCM:0", + "Garbage", + NULL, + "0"}; + + while (next_token(arg, tok, LIST_SEP, sizeof(tok))) { + + for (i=0; i < ARRAY_SIZE(cctypes); i++) { + if (!wbinfo_auth_krb5(tok, cctypes[i], flags)) { + d_fprintf(stderr, "Could not authenticate user [%s] with " + "Kerberos (ccache: %s)\n", tok, cctypes[i]); + got_error = True; + } + } + } + + if (got_error) + goto done; + + break; + } case 'k': if (!wbinfo_klog(string_arg)) { d_fprintf(stderr, "Could not klog user\n"); @@ -1198,7 +1325,7 @@ int main(int argc, char **argv) break; case 'p': if (!wbinfo_ping()) { - d_fprintf(stderr, "Could not ping winbindd!\n"); + d_fprintf(stderr, "could not ping winbindd!\n"); goto done; } break; @@ -1223,6 +1350,10 @@ int main(int argc, char **argv) d_printf("%c\n", sep); break; } + case OPT_LIST_ALL_DOMAINS: + if (!wbinfo_list_domains(True)) { + goto done; + } /* generic configuration options */ case OPT_DOMAIN_NAME: break; diff --git a/source3/nsswitch/winbindd.c b/source3/nsswitch/winbindd.c index bbcf2b5e88..4a269bac17 100644 --- a/source3/nsswitch/winbindd.c +++ b/source3/nsswitch/winbindd.c @@ -120,7 +120,7 @@ static void winbindd_status(void) if (DEBUGLEVEL >= 2 && winbindd_num_clients()) { DEBUG(2, ("\tclient list:\n")); for(tmp = winbindd_client_list(); tmp; tmp = tmp->next) { - DEBUG(2, ("\t\tpid %lu, sock %d\n", + DEBUGADD(2, ("\t\tpid %lu, sock %d\n", (unsigned long)tmp->pid, tmp->sock)); } } @@ -250,6 +250,7 @@ static struct winbindd_dispatch_table { { WINBINDD_PAM_AUTH, winbindd_pam_auth, "PAM_AUTH" }, { WINBINDD_PAM_AUTH_CRAP, winbindd_pam_auth_crap, "AUTH_CRAP" }, { WINBINDD_PAM_CHAUTHTOK, winbindd_pam_chauthtok, "CHAUTHTOK" }, + { WINBINDD_PAM_LOGOFF, winbindd_pam_logoff, "PAM_LOGOFF" }, /* Enumeration functions */ @@ -270,9 +271,8 @@ static struct winbindd_dispatch_table { { WINBINDD_SID_TO_GID, winbindd_sid_to_gid, "SID_TO_GID" }, { WINBINDD_UID_TO_SID, winbindd_uid_to_sid, "UID_TO_SID" }, { WINBINDD_GID_TO_SID, winbindd_gid_to_sid, "GID_TO_SID" }, - { WINBINDD_ALLOCATE_RID, winbindd_allocate_rid, "ALLOCATE_RID" }, - { WINBINDD_ALLOCATE_RID_AND_GID, winbindd_allocate_rid_and_gid, - "ALLOCATE_RID_AND_GID" }, + { WINBINDD_ALLOCATE_UID, winbindd_allocate_uid, "ALLOCATE_UID" }, + { WINBINDD_ALLOCATE_GID, winbindd_allocate_uid, "ALLOCATE_GID" }, /* Miscellaneous */ @@ -1062,7 +1062,11 @@ int main(int argc, char **argv) as to SIGHUP signal */ message_register(MSG_SMB_CONF_UPDATED, msg_reload_services); message_register(MSG_SHUTDOWN, msg_shutdown); - + + /* Handle online/offline messages. */ + message_register(MSG_WINBIND_OFFLINE,winbind_msg_offline); + message_register(MSG_WINBIND_ONLINE,winbind_msg_online); + poptFreeContext(pc); netsamlogon_cache_init(); /* Non-critical */ diff --git a/source3/nsswitch/winbindd.h b/source3/nsswitch/winbindd.h index 00a0233055..e81102571c 100644 --- a/source3/nsswitch/winbindd.h +++ b/source3/nsswitch/winbindd.h @@ -143,7 +143,9 @@ struct winbindd_child { struct winbindd_domain *domain; pstring logfilename; + TALLOC_CTX *mem_ctx; struct fd_event event; + struct timed_event *timed_event; struct winbindd_async_request *requests; }; @@ -157,7 +159,8 @@ struct winbindd_domain { BOOL native_mode; /* is this a win2k domain in native mode ? */ BOOL active_directory; /* is this a win2k active directory ? */ BOOL primary; /* is this our primary domain ? */ - BOOL internal; /* BUILTIN and member SAM */ + BOOL internal; /* BUILTIN and member SAM */ + BOOL online; /* is this domain available ? */ /* Lookup methods for this domain (LDAP or RPC) */ struct winbindd_methods *methods; @@ -268,6 +271,16 @@ struct winbindd_methods { /* return the current global sequence number */ NTSTATUS (*sequence_number)(struct winbindd_domain *domain, uint32 *seq); + /* return the lockout policy */ + NTSTATUS (*lockout_policy)(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + SAM_UNK_INFO_12 *lockout_policy); + + /* return the lockout policy */ + NTSTATUS (*password_policy)(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + SAM_UNK_INFO_1 *password_policy); + /* enumerate trusted domains */ NTSTATUS (*trusted_domains)(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, @@ -305,7 +318,7 @@ struct winbindd_idmap_methods { #define WINBINDD_ESTABLISH_LOOP 30 #define WINBINDD_RESCAN_FREQ 300 - +#define WINBINDD_PAM_AUTH_KRB5_RENEW_TIME 2592000 /* one month */ #define DOM_SEQUENCE_NONE ((uint32)-1) #endif /* _WINBINDD_H */ diff --git a/source3/nsswitch/winbindd_ads.c b/source3/nsswitch/winbindd_ads.c index 29129e823a..a866365042 100644 --- a/source3/nsswitch/winbindd_ads.c +++ b/source3/nsswitch/winbindd_ads.c @@ -102,6 +102,8 @@ static ADS_STRUCT *ads_cached_connection(struct winbindd_domain *domain) ads->auth.realm = SMB_STRDUP( lp_realm() ); } + ads->auth.renewable = 1; + status = ads_connect(ads); if (!ADS_ERR_OK(status) || !ads->config.realm) { extern struct winbindd_methods msrpc_methods, cache_methods; @@ -206,8 +208,10 @@ static NTSTATUS query_user_list(struct winbindd_domain *domain, name = ads_pull_username(ads, mem_ctx, msg); gecos = ads_pull_string(ads, mem_ctx, msg, "name"); if (use_nss_info("sfu")) { - homedir = ads_pull_string(ads, mem_ctx, msg, ads->schema.sfu_homedir_attr); - shell = ads_pull_string(ads, mem_ctx, msg, ads->schema.sfu_shell_attr); + homedir = ads_pull_string(ads, mem_ctx, msg, + ads->schema.sfu_homedir_attr); + shell = ads_pull_string(ads, mem_ctx, msg, + ads->schema.sfu_shell_attr); } if (!ads_pull_sid(ads, msg, "objectSid", @@ -474,8 +478,10 @@ static NTSTATUS query_user(struct winbindd_domain *domain, info->full_name = ads_pull_string(ads, mem_ctx, msg, "name"); if (use_nss_info("sfu")) { - info->homedir = ads_pull_string(ads, mem_ctx, msg, ads->schema.sfu_homedir_attr); - info->shell = ads_pull_string(ads, mem_ctx, msg, ads->schema.sfu_shell_attr); + info->homedir = ads_pull_string(ads, mem_ctx, msg, + ads->schema.sfu_homedir_attr); + info->shell = ads_pull_string(ads, mem_ctx, msg, + ads->schema.sfu_shell_attr); } if (!ads_pull_uint32(ads, msg, "primaryGroupID", &group_rid)) { @@ -872,8 +878,7 @@ static NTSTATUS trusted_domains(struct winbindd_domain *domain, struct ds_domain_trust *domains = NULL; int count = 0; int i; - /* i think we only need our forest and downlevel trusted domains */ - uint32 flags = DS_DOMAIN_IN_FOREST | DS_DOMAIN_DIRECT_OUTBOUND; + uint32 flags = DS_DOMAIN_DIRECT_OUTBOUND; struct rpc_pipe_client *cli; DEBUG(3,("ads: trusted_domains\n")); @@ -946,6 +951,8 @@ struct winbindd_methods ads_methods = { msrpc_lookup_useraliases, lookup_groupmem, sequence_number, + msrpc_lockout_policy, + msrpc_password_policy, trusted_domains, }; diff --git a/source3/nsswitch/winbindd_cache.c b/source3/nsswitch/winbindd_cache.c index 9ecfb1ff6e..28633757c0 100644 --- a/source3/nsswitch/winbindd_cache.c +++ b/source3/nsswitch/winbindd_cache.c @@ -6,7 +6,7 @@ Copyright (C) Andrew Tridgell 2001 Copyright (C) Gerald Carter 2003 Copyright (C) Volker Lendecke 2005 - + Copyright (C) Guenther Deschner 2005 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 @@ -29,6 +29,12 @@ #undef DBGC_CLASS #define DBGC_CLASS DBGC_WINBIND +/* Global online/offline state - False when online. winbindd starts up online + and sets this to true if the first query fails and there's an entry in + the cache tdb telling us to stay offline. */ + +static BOOL global_winbindd_offline_state; + struct winbind_cache { TDB_CONTEXT *tdb; }; @@ -44,29 +50,6 @@ struct cache_entry { static struct winbind_cache *wcache; -/* flush the cache */ -void wcache_flush_cache(void) -{ - extern BOOL opt_nocache; - - if (!wcache) - return; - if (wcache->tdb) { - tdb_close(wcache->tdb); - wcache->tdb = NULL; - } - if (opt_nocache) - return; - - wcache->tdb = tdb_open_log(lock_path("winbindd_cache.tdb"), 5000, - TDB_CLEAR_IF_FIRST, O_RDWR|O_CREAT, 0600); - - if (!wcache->tdb) { - DEBUG(0,("Failed to open winbindd_cache.tdb!\n")); - } - DEBUG(10,("wcache_flush_cache success\n")); -} - void winbindd_check_cache_size(time_t t) { static time_t last_check_time; @@ -187,6 +170,22 @@ static uint32 centry_uint32(struct cache_entry *centry) return ret; } +/* + pull a uint16 from a cache entry +*/ +static uint16 centry_uint16(struct cache_entry *centry) +{ + uint16 ret; + if (centry->len - centry->ofs < 2) { + DEBUG(0,("centry corruption? needed 2 bytes, have %d\n", + centry->len - centry->ofs)); + smb_panic("centry_uint16"); + } + ret = CVAL(centry->data, centry->ofs); + centry->ofs += 2; + return ret; +} + /* pull a uint8 from a cache entry */ @@ -203,6 +202,40 @@ static uint8 centry_uint8(struct cache_entry *centry) return ret; } +/* + pull a NTTIME from a cache entry +*/ +static NTTIME centry_nttime(struct cache_entry *centry) +{ + NTTIME ret; + if (centry->len - centry->ofs < 8) { + DEBUG(0,("centry corruption? needed 8 bytes, have %d\n", + centry->len - centry->ofs)); + smb_panic("centry_nttime"); + } + ret.low = IVAL(centry->data, centry->ofs); + centry->ofs += 4; + ret.high = IVAL(centry->data, centry->ofs); + centry->ofs += 4; + return ret; +} + +/* + pull a time_t from a cache entry +*/ +static time_t centry_time(struct cache_entry *centry) +{ + time_t ret; + if (centry->len - centry->ofs < sizeof(time_t)) { + DEBUG(0,("centry corruption? needed %d bytes, have %d\n", + sizeof(time_t), centry->len - centry->ofs)); + smb_panic("centry_time"); + } + ret = IVAL(centry->data, centry->ofs); /* FIXME: correct ? */ + centry->ofs += sizeof(time_t); + return ret; +} + /* pull a string from a cache entry, using the supplied talloc context */ @@ -403,10 +436,28 @@ done: */ static BOOL centry_expired(struct winbindd_domain *domain, const char *keystr, struct cache_entry *centry) { + /* If we've been told to be offline - stay in that state... */ + if (lp_winbind_offline_logon() && global_winbindd_offline_state) { + DEBUG(10,("centry_expired: Key %s for domain %s valid as winbindd is globally offline.\n", + keystr, domain->name )); + return False; + } + + /* when the domain is offline and we havent checked in the last 30 + * seconds if it has become online again, return the cached entry. + * This deals with transient offline states... */ + + if (!domain->online && + !NT_STATUS_IS_OK(check_negative_conn_cache(domain->name, domain->dcname))) { + DEBUG(10,("centry_expired: Key %s for domain %s valid as domain is offline.\n", + keystr, domain->name )); + return False; + } + /* if the server is OK and our cache entry came from when it was down then the entry is invalid */ - if (domain->sequence_number != DOM_SEQUENCE_NONE && - centry->sequence_number == DOM_SEQUENCE_NONE) { + if ((domain->sequence_number != DOM_SEQUENCE_NONE) && + (centry->sequence_number == DOM_SEQUENCE_NONE)) { DEBUG(10,("centry_expired: Key %s for domain %s invalid sequence.\n", keystr, domain->name )); return True; @@ -428,35 +479,17 @@ static BOOL centry_expired(struct winbindd_domain *domain, const char *keystr, s return True; } -/* - fetch an entry from the cache, with a varargs key. auto-fetch the sequence - number and return status -*/ -static struct cache_entry *wcache_fetch(struct winbind_cache *cache, - struct winbindd_domain *domain, - const char *format, ...) PRINTF_ATTRIBUTE(3,4); -static struct cache_entry *wcache_fetch(struct winbind_cache *cache, - struct winbindd_domain *domain, - const char *format, ...) +static struct cache_entry *wcache_fetch_raw(char *kstr) { - va_list ap; - char *kstr; TDB_DATA data; struct cache_entry *centry; TDB_DATA key; - refresh_sequence_number(domain, False); - - va_start(ap, format); - smb_xvasprintf(&kstr, format, ap); - va_end(ap); - key.dptr = kstr; key.dsize = strlen(kstr); data = tdb_fetch(wcache->tdb, key); if (!data.dptr) { /* a cache miss */ - free(kstr); return NULL; } @@ -467,16 +500,44 @@ static struct cache_entry *wcache_fetch(struct winbind_cache *cache, if (centry->len < 8) { /* huh? corrupt cache? */ - DEBUG(10,("wcache_fetch: Corrupt cache for key %s domain %s (len < 8) ?\n", - kstr, domain->name )); + DEBUG(10,("wcache_fetch_raw: Corrupt cache for key %s (len < 8) ?\n", kstr)); centry_free(centry); - free(kstr); return NULL; } centry->status = NT_STATUS(centry_uint32(centry)); centry->sequence_number = centry_uint32(centry); + return centry; +} + +/* + fetch an entry from the cache, with a varargs key. auto-fetch the sequence + number and return status +*/ +static struct cache_entry *wcache_fetch(struct winbind_cache *cache, + struct winbindd_domain *domain, + const char *format, ...) PRINTF_ATTRIBUTE(3,4); +static struct cache_entry *wcache_fetch(struct winbind_cache *cache, + struct winbindd_domain *domain, + const char *format, ...) +{ + va_list ap; + char *kstr; + struct cache_entry *centry; + + refresh_sequence_number(domain, False); + + va_start(ap, format); + smb_xvasprintf(&kstr, format, ap); + va_end(ap); + + centry = wcache_fetch_raw(kstr); + if (centry == NULL) { + free(kstr); + return NULL; + } + if (centry_expired(domain, kstr, centry)) { DEBUG(10,("wcache_fetch: entry %s expired for domain %s\n", @@ -521,6 +582,16 @@ static void centry_put_uint32(struct cache_entry *centry, uint32 v) centry->ofs += 4; } +/* + push a uint16 into a centry +*/ +static void centry_put_uint16(struct cache_entry *centry, uint16 v) +{ + centry_expand(centry, 2); + SIVAL(centry->data, centry->ofs, v); + centry->ofs += 2; +} + /* push a uint8 into a centry */ @@ -562,6 +633,28 @@ static void centry_put_sid(struct cache_entry *centry, const DOM_SID *sid) centry_put_string(centry, sid_to_string(sid_string, sid)); } +/* + push a NTTIME into a centry +*/ +static void centry_put_nttime(struct cache_entry *centry, NTTIME nt) +{ + centry_expand(centry, 8); + SIVAL(centry->data, centry->ofs, nt.low); + centry->ofs += 4; + SIVAL(centry->data, centry->ofs, nt.high); + centry->ofs += 4; +} + +/* + push a time_t into a centry +*/ +static void centry_put_time(struct cache_entry *centry, time_t t) +{ + centry_expand(centry, sizeof(time_t)); + SIVAL(centry->data, centry->ofs, t); /* FIXME: is this correct ?? */ + centry->ofs += sizeof(time_t); +} + /* start a centry for output. When finished, call centry_end() */ @@ -666,6 +759,129 @@ static void wcache_save_user(struct winbindd_domain *domain, NTSTATUS status, WI centry_free(centry); } +static void wcache_save_lockout_policy(struct winbindd_domain *domain, NTSTATUS status, SAM_UNK_INFO_12 *lockout_policy) +{ + struct cache_entry *centry; + + centry = centry_start(domain, status); + if (!centry) + return; + + centry_put_nttime(centry, lockout_policy->duration); + centry_put_nttime(centry, lockout_policy->reset_count); + centry_put_uint16(centry, lockout_policy->bad_attempt_lockout); + + centry_end(centry, "LOC_POL/%s", domain->name); + + DEBUG(10,("wcache_save_lockout_policy: %s\n", domain->name)); + + centry_free(centry); +} + +static void wcache_save_password_policy(struct winbindd_domain *domain, NTSTATUS status, SAM_UNK_INFO_1 *password_policy) +{ + struct cache_entry *centry; + + centry = centry_start(domain, status); + if (!centry) + return; + + centry_put_uint16(centry, password_policy->min_length_password); + centry_put_uint16(centry, password_policy->password_history); + centry_put_uint32(centry, password_policy->password_properties); + centry_put_nttime(centry, password_policy->expire); + centry_put_nttime(centry, password_policy->min_passwordage); + + centry_end(centry, "PWD_POL/%s", domain->name); + + DEBUG(10,("wcache_save_password_policy: %s\n", domain->name)); + + centry_free(centry); +} + +NTSTATUS wcache_cached_creds_exist(struct winbindd_domain *domain, const DOM_SID *sid) +{ + struct winbind_cache *cache = get_cache(domain); + TDB_DATA data; + fstring key_str; + + if (!cache->tdb) { + return NT_STATUS_INTERNAL_DB_ERROR; + } + + fstr_sprintf(key_str, "CRED/%s", sid_string_static(sid)); + + data = tdb_fetch(cache->tdb, make_tdb_data(key_str, strlen(key_str))); + if (!data.dptr) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + return NT_STATUS_OK; +} + +/* Lookup creds for a SID */ +NTSTATUS wcache_get_creds(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const DOM_SID *sid, + const uint8 **cached_nt_pass) +{ + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + NTSTATUS status; + time_t t; + + if (!cache->tdb) { + return NT_STATUS_INTERNAL_DB_ERROR; + } + + centry = wcache_fetch(cache, domain, "CRED/%s", sid_string_static(sid)); + + if (!centry) { + DEBUG(10,("wcache_get_creds: entry for [CRED/%s] not found\n", + sid_string_static(sid))); + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + t = centry_time(centry); + *cached_nt_pass = (const uint8 *)centry_string(centry, mem_ctx); + + dump_data(10, (const char *)cached_nt_pass, NT_HASH_LEN); + status = centry->status; + + DEBUG(10,("wcache_get_creds: [Cached] - cached creds for user %s status %s\n", + sid_string_static(sid), get_friendly_nt_error_msg(status) )); + + centry_free(centry); + return status; +} + +NTSTATUS wcache_save_creds(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const DOM_SID *sid, + const uint8 nt_pass[NT_HASH_LEN]) +{ + struct cache_entry *centry; + fstring sid_string; + NTSTATUS status = NT_STATUS_OK; /* ??? */ + + centry = centry_start(domain, status); + if (!centry) { + return NT_STATUS_INTERNAL_DB_ERROR; + } + + dump_data(100, (const char *)nt_pass, NT_HASH_LEN); + + centry_put_time(centry, time(NULL)); + centry_put_string(centry, (const char *)nt_pass); + centry_end(centry, "CRED/%s", sid_to_string(sid_string, sid)); + + DEBUG(10,("wcache_save_creds: %s\n", sid_string)); + + centry_free(centry); + + return NT_STATUS_OK; +} + /* Query display info. This is the basic user list fn */ static NTSTATUS query_user_list(struct winbindd_domain *domain, @@ -991,7 +1207,9 @@ do_query: status = domain->backend->name_to_sid(domain, mem_ctx, domain_name, name, sid, type); /* and save it */ - wcache_save_name_to_sid(domain, status, domain_name, name, sid, *type); + if (domain->online || !is_null_sid(sid)) { + wcache_save_name_to_sid(domain, status, domain_name, name, sid, *type); + } if (NT_STATUS_IS_OK(status)) { strupper_m(CONST_DISCARD(char *,domain_name)); @@ -1390,7 +1608,9 @@ static NTSTATUS sequence_number(struct winbindd_domain *domain, uint32 *seq) return NT_STATUS_OK; } -/* enumerate trusted domains */ +/* enumerate trusted domains + * (we need to have the list of trustdoms in the cache when we go offline) - + * Guenther */ static NTSTATUS trusted_domains(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, uint32 *num_domains, @@ -1398,16 +1618,184 @@ static NTSTATUS trusted_domains(struct winbindd_domain *domain, char ***alt_names, DOM_SID **dom_sids) { - get_cache(domain); + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + NTSTATUS status; + int i; + + if (!cache->tdb) + goto do_query; + + centry = wcache_fetch(cache, domain, "TRUSTDOMS/%s", domain->name); + + if (!centry) { + goto do_query; + } + + *num_domains = centry_uint32(centry); + + (*names) = TALLOC_ARRAY(mem_ctx, char *, *num_domains); + (*alt_names) = TALLOC_ARRAY(mem_ctx, char *, *num_domains); + (*dom_sids) = TALLOC_ARRAY(mem_ctx, DOM_SID, *num_domains); + + if (! (*dom_sids) || ! (*names) || ! (*alt_names)) { + smb_panic("trusted_domains out of memory"); + } + + for (i=0; i<(*num_domains); i++) { + (*names)[i] = centry_string(centry, mem_ctx); + (*alt_names)[i] = centry_string(centry, mem_ctx); + centry_sid(centry, &(*dom_sids)[i]); + } + status = centry->status; + + DEBUG(10,("trusted_domains: [Cached] - cached info for domain %s status %s\n", + domain->name, get_friendly_nt_error_msg(status) )); + + centry_free(centry); + return status; + +do_query: + (*num_domains) = 0; + (*dom_sids) = NULL; + (*names) = NULL; + (*alt_names) = NULL; + + /* Return status value returned by seq number check */ + + if (!NT_STATUS_IS_OK(domain->last_status)) + return domain->last_status; + DEBUG(10,("trusted_domains: [Cached] - doing backend query for info for domain %s\n", domain->name )); + + status = domain->backend->trusted_domains(domain, mem_ctx, num_domains, + names, alt_names, dom_sids); + + /* and save it */ + refresh_sequence_number(domain, False); + + centry = centry_start(domain, status); + if (!centry) + goto skip_save; + + centry_put_uint32(centry, *num_domains); + + for (i=0; i<(*num_domains); i++) { + centry_put_string(centry, (*names)[i]); + centry_put_string(centry, (*alt_names)[i]); + centry_put_sid(centry, &(*dom_sids)[i]); + } + + centry_end(centry, "TRUSTDOMS/%s", domain->name); + + centry_free(centry); + +skip_save: + return status; +} + +/* get lockout policy */ +static NTSTATUS lockout_policy(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + SAM_UNK_INFO_12 *lockout_policy){ + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + NTSTATUS status; + + if (!cache->tdb) + goto do_query; + + centry = wcache_fetch(cache, domain, "LOC_POL/%s", domain->name); + + if (!centry) + goto do_query; + + lockout_policy->duration = centry_nttime(centry); + lockout_policy->reset_count = centry_nttime(centry); + lockout_policy->bad_attempt_lockout = centry_uint16(centry); + + status = centry->status; + + DEBUG(10,("lockout_policy: [Cached] - cached info for domain %s status %s\n", + domain->name, get_friendly_nt_error_msg(status) )); + + centry_free(centry); + return status; + +do_query: + ZERO_STRUCTP(lockout_policy); + + /* Return status value returned by seq number check */ + + if (!NT_STATUS_IS_OK(domain->last_status)) + return domain->last_status; + + DEBUG(10,("lockout_policy: [Cached] - doing backend query for info for domain %s\n", + domain->name )); + + status = domain->backend->lockout_policy(domain, mem_ctx, lockout_policy); + + /* and save it */ + refresh_sequence_number(domain, False); + wcache_save_lockout_policy(domain, status, lockout_policy); + + return status; +} + +/* get password policy */ +static NTSTATUS password_policy(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + SAM_UNK_INFO_1 *password_policy) +{ + struct winbind_cache *cache = get_cache(domain); + struct cache_entry *centry = NULL; + NTSTATUS status; + + if (!cache->tdb) + goto do_query; + + centry = wcache_fetch(cache, domain, "PWD_POL/%s", domain->name); + + if (!centry) + goto do_query; + + password_policy->min_length_password = centry_uint16(centry); + password_policy->password_history = centry_uint16(centry); + password_policy->password_properties = centry_uint32(centry); + password_policy->expire = centry_nttime(centry); + password_policy->min_passwordage = centry_nttime(centry); + + status = centry->status; + + DEBUG(10,("lockout_policy: [Cached] - cached info for domain %s status %s\n", + domain->name, get_friendly_nt_error_msg(status) )); + + centry_free(centry); + return status; + +do_query: + ZERO_STRUCTP(password_policy); + + /* Return status value returned by seq number check */ + + if (!NT_STATUS_IS_OK(domain->last_status)) + return domain->last_status; + + DEBUG(10,("password_policy: [Cached] - doing backend query for info for domain %s\n", + domain->name )); + + status = domain->backend->password_policy(domain, mem_ctx, password_policy); + + /* and save it */ + refresh_sequence_number(domain, False); + wcache_save_password_policy(domain, status, password_policy); - /* we don't cache this call */ - return domain->backend->trusted_domains(domain, mem_ctx, num_domains, - names, alt_names, dom_sids); + return status; } + /* Invalidate cached user and group lists coherently */ static int traverse_fn(TDB_CONTEXT *the_tdb, TDB_DATA kbuf, TDB_DATA dbuf, @@ -1448,22 +1836,6 @@ void wcache_invalidate_cache(void) } } -/* the ADS backend methods are exposed via this structure */ -struct winbindd_methods cache_methods = { - True, - query_user_list, - enum_dom_groups, - enum_local_groups, - name_to_sid, - sid_to_name, - query_user, - lookup_usergroups, - lookup_useraliases, - lookup_groupmem, - sequence_number, - trusted_domains, -}; - static BOOL init_wcache(void) { if (wcache == NULL) { @@ -1474,8 +1846,10 @@ static BOOL init_wcache(void) if (wcache->tdb != NULL) return True; - wcache->tdb = tdb_open_log(lock_path("winbindd_cache.tdb"), 5000, - TDB_CLEAR_IF_FIRST, O_RDWR|O_CREAT, 0600); + /* when working offline we must not clear the cache on restart */ + wcache->tdb = tdb_open_log(lock_path("winbindd_cache.tdb"), + WINBINDD_CACHE_TDB_DEFAULT_HASH_SIZE, + TDB_DEFAULT /*TDB_CLEAR_IF_FIRST*/, O_RDWR|O_CREAT, 0600); if (wcache->tdb == NULL) { DEBUG(0,("Failed to open winbindd_cache.tdb!\n")); @@ -1577,6 +1951,25 @@ BOOL cache_retrieve_response(pid_t pid, struct winbindd_response * response) return True; } +void cache_cleanup_response(pid_t pid) +{ + fstring key_str; + + if (!init_wcache()) + return; + + DEBUG(10,("Cleaning up response for pid %d\n", pid)); + + fstr_sprintf(key_str, "DR/%d", pid); + tdb_delete(wcache->tdb, string_tdb_data(key_str)); + + fstr_sprintf(key_str, "DE/%d", pid); + tdb_delete(wcache->tdb, string_tdb_data(key_str)); + + return; +} + + BOOL lookup_cached_sid(TALLOC_CTX *mem_ctx, const DOM_SID *sid, const char **domain_name, const char **name, enum SID_NAME_USE *type) @@ -1613,6 +2006,48 @@ BOOL lookup_cached_sid(TALLOC_CTX *mem_ctx, const DOM_SID *sid, return NT_STATUS_IS_OK(status); } +BOOL lookup_cached_name(TALLOC_CTX *mem_ctx, + const char *domain_name, + const char *name, + DOM_SID *sid, + enum SID_NAME_USE *type) +{ + struct winbindd_domain *domain; + struct winbind_cache *cache; + struct cache_entry *centry = NULL; + NTSTATUS status; + fstring uname; + + domain = find_lookup_domain_from_name(domain_name); + if (domain == NULL) { + return False; + } + + cache = get_cache(domain); + + if (cache->tdb == NULL) { + return False; + } + + fstrcpy(uname, name); + strupper_m(uname); + + centry = wcache_fetch(cache, domain, "NS/%s/%s", domain_name, uname); + if (centry == NULL) { + return False; + } + + if (NT_STATUS_IS_OK(centry->status)) { + *type = (enum SID_NAME_USE)centry_uint32(centry); + centry_sid(centry, sid); + } + + status = centry->status; + centry_free(centry); + + return NT_STATUS_IS_OK(status); +} + void cache_sid2name(struct winbindd_domain *domain, const DOM_SID *sid, const char *domain_name, const char *name, enum SID_NAME_USE type) @@ -1620,3 +2055,282 @@ void cache_sid2name(struct winbindd_domain *domain, const DOM_SID *sid, wcache_save_sid_to_name(domain, NT_STATUS_OK, sid, domain_name, name, type); } + +/* delete all centries that don't have NT_STATUS_OK set */ +static int traverse_fn_cleanup(TDB_CONTEXT *the_tdb, TDB_DATA kbuf, + TDB_DATA dbuf, void *state) +{ + struct cache_entry *centry; + char buf[1024]; + + if (!snprintf(buf, kbuf.dsize + 1, "%s", kbuf.dptr)) { + return 1; + } + + centry = wcache_fetch_raw(buf); + if (!centry) { + return 0; + } + + if (!NT_STATUS_IS_OK(centry->status)) { + DEBUG(10,("deleting centry %s\n", buf)); + tdb_delete(the_tdb, kbuf); + } + + centry_free(centry); + return 0; +} + +/* flush the cache */ +void wcache_flush_cache(void) +{ + extern BOOL opt_nocache; + + if (!wcache) + return; + if (wcache->tdb) { + tdb_close(wcache->tdb); + wcache->tdb = NULL; + } + if (opt_nocache) + return; + + /* when working offline we must not clear the cache on restart */ + wcache->tdb = tdb_open_log(lock_path("winbindd_cache.tdb"), + WINBINDD_CACHE_TDB_DEFAULT_HASH_SIZE, + TDB_DEFAULT /* TDB_CLEAR_IF_FIRST */, O_RDWR|O_CREAT, 0600); + + if (!wcache->tdb) { + DEBUG(0,("Failed to open winbindd_cache.tdb!\n")); + } + + tdb_traverse(wcache->tdb, traverse_fn_cleanup, NULL); + + DEBUG(10,("wcache_flush_cache success\n")); +} + +/* Count cached creds */ + +static int traverse_fn_cached_creds(TDB_CONTEXT *the_tdb, TDB_DATA kbuf, TDB_DATA dbuf, + void *state) +{ + int *cred_count = (int*)state; + + if (strncmp(kbuf.dptr, "CRED/", 5) == 0) { + (*cred_count)++; + } + return 0; +} + +NTSTATUS wcache_count_cached_creds(struct winbindd_domain *domain, int *count) +{ + struct winbind_cache *cache = get_cache(domain); + + *count = 0; + + if (!cache->tdb) { + return NT_STATUS_INTERNAL_DB_ERROR; + } + + tdb_traverse(cache->tdb, traverse_fn_cached_creds, (void *)count); + + return NT_STATUS_OK; +} + +struct cred_list { + struct cred_list *prev, *next; + TDB_DATA key; + fstring name; + time_t created; +}; +static struct cred_list *wcache_cred_list; + +static int traverse_fn_get_credlist(TDB_CONTEXT *the_tdb, TDB_DATA kbuf, TDB_DATA dbuf, + void *state) +{ + struct cred_list *cred; + + if (strncmp(kbuf.dptr, "CRED/", 5) == 0) { + + cred = SMB_MALLOC_P(struct cred_list); + if (cred == NULL) { + DEBUG(0,("traverse_fn_remove_first_creds: failed to malloc new entry for list\n")); + return -1; + } + + ZERO_STRUCTP(cred); + + /* save a copy of the key */ + + fstrcpy(cred->name, kbuf.dptr); + DLIST_ADD(wcache_cred_list, cred); + } + + return 0; +} + +NTSTATUS wcache_remove_oldest_cached_creds(struct winbindd_domain *domain, const DOM_SID *sid) +{ + struct winbind_cache *cache = get_cache(domain); + NTSTATUS status; + int ret; + struct cred_list *cred, *oldest = NULL; + + if (!cache->tdb) { + return NT_STATUS_INTERNAL_DB_ERROR; + } + + /* we possibly already have an entry */ + if (sid && NT_STATUS_IS_OK(wcache_cached_creds_exist(domain, sid))) { + + fstring key_str; + + DEBUG(11,("we already have an entry, deleting that\n")); + + fstr_sprintf(key_str, "CRED/%s", sid_string_static(sid)); + + tdb_delete(cache->tdb, string_tdb_data(key_str)); + + return NT_STATUS_OK; + } + + ret = tdb_traverse(cache->tdb, traverse_fn_get_credlist, NULL); + if (ret == 0) { + return NT_STATUS_OK; + } else if (ret == -1) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + ZERO_STRUCTP(oldest); + + for (cred = wcache_cred_list; cred; cred = cred->next) { + + TDB_DATA data; + time_t t; + + data = tdb_fetch(cache->tdb, make_tdb_data(cred->name, strlen(cred->name))); + if (!data.dptr) { + DEBUG(10,("wcache_remove_oldest_cached_creds: entry for [%s] not found\n", + cred->name)); + status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + goto done; + } + + t = IVAL(data.dptr, 0); + SAFE_FREE(data.dptr); + + if (!oldest) { + oldest = SMB_MALLOC_P(struct cred_list); + if (oldest == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + fstrcpy(oldest->name, cred->name); + oldest->created = t; + continue; + } + + if (t < oldest->created) { + fstrcpy(oldest->name, cred->name); + oldest->created = t; + } + } + + if (tdb_delete(cache->tdb, string_tdb_data(oldest->name)) == 0) { + status = NT_STATUS_OK; + } else { + status = NT_STATUS_UNSUCCESSFUL; + } +done: + SAFE_FREE(wcache_cred_list); + SAFE_FREE(oldest); + + return status; +} + +/* Change the global online/offline state. */ +BOOL set_global_winbindd_state_offline(void) +{ + TDB_DATA data; + int err; + + DEBUG(10,("set_global_winbindd_state_offline: offline requested.\n")); + + /* Only go offline if someone has created + the key "WINBINDD_OFFLINE" in the cache tdb. */ + + if (wcache == NULL || wcache->tdb == NULL) { + DEBUG(10,("set_global_winbindd_state_offline: wcache not open yet.\n")); + return False; + } + + if (!lp_winbind_offline_logon()) { + DEBUG(10,("set_global_winbindd_state_offline: rejecting.\n")); + return False; + } + + if (global_winbindd_offline_state) { + /* Already offline. */ + return True; + } + + wcache->tdb->ecode = 0; + + data = tdb_fetch_bystring( wcache->tdb, "WINBINDD_OFFLINE" ); + + /* As this is a key with no data we don't need to free, we + check for existence by looking at tdb_err. */ + + err = tdb_error(wcache->tdb); + + if (err == TDB_ERR_NOEXIST) { + DEBUG(10,("set_global_winbindd_state_offline: offline state not set.\n")); + return False; + } else { + DEBUG(10,("set_global_winbindd_state_offline: offline state set.\n")); + global_winbindd_offline_state = True; + return True; + } +} + +void set_global_winbindd_state_online(void) +{ + DEBUG(10,("set_global_winbindd_state_online: online requested.\n")); + + if (!lp_winbind_offline_logon()) { + DEBUG(10,("set_global_winbindd_state_online: rejecting.\n")); + return; + } + + if (!global_winbindd_offline_state) { + /* Already online. */ + return; + } + global_winbindd_offline_state = False; + + if (!wcache->tdb) { + return; + } + + /* Ensure there is no key "WINBINDD_OFFLINE" in the cache tdb. */ + tdb_delete_bystring(wcache->tdb, "WINBINDD_OFFLINE"); +} + +/* the cache backend methods are exposed via this structure */ +struct winbindd_methods cache_methods = { + True, + query_user_list, + enum_dom_groups, + enum_local_groups, + name_to_sid, + sid_to_name, + query_user, + lookup_usergroups, + lookup_useraliases, + lookup_groupmem, + sequence_number, + lockout_policy, + password_policy, + trusted_domains +}; diff --git a/source3/nsswitch/winbindd_cm.c b/source3/nsswitch/winbindd_cm.c index 177ac54d3e..568078f86e 100644 --- a/source3/nsswitch/winbindd_cm.c +++ b/source3/nsswitch/winbindd_cm.c @@ -784,26 +784,32 @@ 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))) + 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)) { + domain->online = False; fd = -1; } } if ((fd == -1) - && !find_new_dc(mem_ctx, domain, domain->dcname, &domain->dcaddr, &fd)) + && !find_new_dc(mem_ctx, domain, domain->dcname, &domain->dcaddr, &fd)) { + /* This is the one place where we will + set the global winbindd offline state + to true, if a "WINBINDD_OFFLINE" entry + is found in the winbindd cache. */ + set_global_winbindd_state_offline(); + domain->online = False; break; } @@ -816,11 +822,19 @@ static NTSTATUS cm_open_connection(struct winbindd_domain *domain, break; } + if (NT_STATUS_IS_OK(result)) { + if (domain->online == False) { + /* We're changing state from offline to online. */ + set_global_winbindd_state_online(); + } + domain->online = True; + } + talloc_destroy(mem_ctx); return result; } -/* Return true if a connection is still alive */ +/* Close down all open pipes on a connection. */ void invalidate_cm_connection(struct winbindd_cm_conn *conn) { diff --git a/source3/nsswitch/winbindd_cred_cache.c b/source3/nsswitch/winbindd_cred_cache.c new file mode 100644 index 0000000000..a8aab04031 --- /dev/null +++ b/source3/nsswitch/winbindd_cred_cache.c @@ -0,0 +1,270 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon - krb5 credential cache funcions + + Copyright (C) Guenther Deschner 2005 + + 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" +#include "winbindd.h" +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +#define MAX_CCACHES 100 + +static struct WINBINDD_CCACHE_ENTRY *ccache_list; + +static TALLOC_CTX *mem_ctx; + +const char *get_ccache_name_by_username(const char *username) +{ + struct WINBINDD_CCACHE_ENTRY *entry; + + for (entry = ccache_list; entry; entry = entry->next) { + if (strequal(entry->username, username)) { + return entry->ccname; + } + } + return NULL; +} + +struct WINBINDD_CCACHE_ENTRY *get_ccache_by_username(const char *username) +{ + struct WINBINDD_CCACHE_ENTRY *entry; + + for (entry = ccache_list; entry; entry = entry->next) { + if (strequal(entry->username, username)) { + return entry; + } + } + return NULL; +} + +static int ccache_entry_count(void) +{ + struct WINBINDD_CCACHE_ENTRY *entry; + int i = 0; + + for (entry = ccache_list; entry; entry = entry->next) { + i++; + } + return i; +} + +NTSTATUS remove_ccache_by_ccname(const char *ccname) +{ + struct WINBINDD_CCACHE_ENTRY *entry; + + for (entry = ccache_list; entry; entry = entry->next) { + if (strequal(entry->ccname, ccname)) { + DLIST_REMOVE(ccache_list, entry); + talloc_free(entry->event); /* unregisters events */ + return talloc_free(entry) ? NT_STATUS_OK : NT_STATUS_UNSUCCESSFUL; + } + } + return NT_STATUS_OBJECT_NAME_NOT_FOUND; +} + +static void krb5_ticket_refresh_handler(struct timed_event *te, + const struct timeval *now, + void *private_data) +{ + struct WINBINDD_CCACHE_ENTRY *entry = + talloc_get_type_abort(private_data, struct WINBINDD_CCACHE_ENTRY); + int ret; + time_t new_start; + struct timeval t; + + + DEBUG(10,("krb5_ticket_refresh_handler called\n")); + DEBUGADD(10,("event called for: %s, %s\n", entry->ccname, entry->username)); + + talloc_free(entry->event); + +#ifdef HAVE_KRB5 + + /* Kinit again if we have the user password and we can't renew the old + * tgt anymore */ + + if ((entry->renew_until < time(NULL)) && (entry->pass != NULL)) { + + seteuid(entry->uid); + + ret = kerberos_kinit_password(entry->principal_name, + entry->pass, + 0, /* hm, can we do time correction here ? */ + &entry->refresh_time, + &entry->renew_until, + entry->ccname, + False, /* no PAC required anymore */ + WINBINDD_PAM_AUTH_KRB5_RENEW_TIME); + seteuid(0); + + if (ret) { + DEBUG(3,("could not re-kinit: %s\n", error_message(ret))); + talloc_free(entry->event); + return; + } + + DEBUG(10,("successful re-kinit for: %s in ccache: %s\n", + entry->principal_name, entry->ccname)); + + new_start = entry->refresh_time; + + goto done; + } + + seteuid(entry->uid); + + ret = smb_krb5_renew_ticket(entry->ccname, + entry->principal_name, + entry->service, + &new_start); + seteuid(0); + + if (ret) { + DEBUG(3,("could not renew tickets: %s\n", error_message(ret))); + /* maybe we are beyond the renewing window */ + return; + } + +done: + + t = timeval_set(new_start, 0); + + entry->event = add_timed_event(mem_ctx, + t, + "krb5_ticket_refresh_handler", + krb5_ticket_refresh_handler, + entry); + +#endif +} + +NTSTATUS add_ccache_to_list(const char *princ_name, + const char *ccname, + const char *service, + const char *username, + const char *sid_string, + const char *pass, + uid_t uid, + time_t create_time, + time_t ticket_end, + time_t renew_until, + BOOL schedule_refresh_event) +{ + struct WINBINDD_CCACHE_ENTRY *new_entry = NULL; + NTSTATUS status; + + if ((username == NULL && sid_string == NULL && princ_name == NULL) || + ccname == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + status = init_ccache_list(); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (mem_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (ccache_entry_count() + 1 > MAX_CCACHES) { + DEBUG(10,("add_ccache_to_list: max number of ccaches reached\n")); + return NT_STATUS_NO_MORE_ENTRIES; + } + + new_entry = TALLOC_P(mem_ctx, struct WINBINDD_CCACHE_ENTRY); + if (new_entry == NULL) { + return NT_STATUS_NO_MEMORY; + } + + ZERO_STRUCTP(new_entry); + + if (username) { + new_entry->username = talloc_strdup(mem_ctx, username); + NT_STATUS_HAVE_NO_MEMORY(new_entry->username); + } + if (sid_string) { + new_entry->sid_string = talloc_strdup(mem_ctx, sid_string); + NT_STATUS_HAVE_NO_MEMORY(new_entry->sid_string); + } + if (princ_name) { + new_entry->principal_name = talloc_strdup(mem_ctx, princ_name); + NT_STATUS_HAVE_NO_MEMORY(new_entry->principal_name); + } + if (service) { + new_entry->service = talloc_strdup(mem_ctx, service); + NT_STATUS_HAVE_NO_MEMORY(new_entry->service); + } + if (pass) { + new_entry->pass = talloc_strdup(mem_ctx, pass); + NT_STATUS_HAVE_NO_MEMORY(new_entry->pass); + } + + new_entry->create_time = create_time; + new_entry->renew_until = renew_until; + new_entry->ccname = talloc_strdup(mem_ctx, ccname); + if (new_entry->ccname == NULL) { + return NT_STATUS_NO_MEMORY; + } + new_entry->uid = uid; + + +#ifndef WITH_KCM /* no point in doing the refresh in KCM and by ourself */ + + if (schedule_refresh_event && renew_until > 0) { + + struct timeval t = timeval_set((ticket_end -1 ), 0); + + new_entry->event = add_timed_event(mem_ctx, + t, + "krb5_ticket_refresh_handler", + krb5_ticket_refresh_handler, + new_entry); + } +#endif /* WITH_KCM */ + + DLIST_ADD(ccache_list, new_entry); + + DEBUG(10,("add_ccache_to_list: added ccache [%s] for user [%s] to the list\n", ccname, username)); + + return NT_STATUS_OK; +} + +NTSTATUS destroy_ccache_list(void) +{ + return talloc_destroy(mem_ctx) ? NT_STATUS_OK : NT_STATUS_UNSUCCESSFUL; +} + +NTSTATUS init_ccache_list(void) +{ + if (ccache_list) { + return NT_STATUS_OK; + } + + mem_ctx = talloc_init("winbindd_ccache_krb5_handling"); + if (mem_ctx == NULL) { + return NT_STATUS_NO_MEMORY; + } + + ZERO_STRUCTP(ccache_list); + + return NT_STATUS_OK; +} diff --git a/source3/nsswitch/winbindd_creds.c b/source3/nsswitch/winbindd_creds.c new file mode 100644 index 0000000000..d37e9019db --- /dev/null +++ b/source3/nsswitch/winbindd_creds.c @@ -0,0 +1,162 @@ +/* + Unix SMB/CIFS implementation. + + Winbind daemon - cached credentials funcions + + Copyright (C) Guenther Deschner 2005 + + 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" +#include "winbindd.h" +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_WINBIND + +#define MAX_CACHED_LOGINS 10 + +NTSTATUS winbindd_get_creds(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const DOM_SID *sid, + NET_USER_INFO_3 **info3, + const uint8 *cached_nt_pass[NT_HASH_LEN]) +{ + NET_USER_INFO_3 *info; + NTSTATUS status; + + status = wcache_get_creds(domain, mem_ctx, sid, cached_nt_pass); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + info = netsamlogon_cache_get(mem_ctx, sid); + if (info == NULL) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + *info3 = info; + + return NT_STATUS_OK; +} + + +NTSTATUS winbindd_store_creds(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const char *user, + const char *pass, + NET_USER_INFO_3 *info3, + const DOM_SID *user_sid) +{ + NTSTATUS status; + uchar nt_pass[NT_HASH_LEN]; + DOM_SID cred_sid; + + if (info3 != NULL) { + + DOM_SID sid; + sid_copy(&sid, &(info3->dom_sid.sid)); + sid_append_rid(&sid, info3->user_rid); + sid_copy(&cred_sid, &sid); + info3->user_flgs |= LOGON_CACHED_ACCOUNT; + + } else if (user_sid != NULL) { + + sid_copy(&cred_sid, user_sid); + + } else if (user != NULL) { + + /* do lookup ourself */ + + enum SID_NAME_USE type; + + if (!lookup_cached_name(mem_ctx, + domain->name, + user, + &cred_sid, + &type)) { + return NT_STATUS_NO_SUCH_USER; + } + } else { + return NT_STATUS_INVALID_PARAMETER; + } + + if (pass) { + + int count = 0; + + status = wcache_count_cached_creds(domain, &count); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + DEBUG(11,("we have %d cached creds\n", count)); + + if (count + 1 > MAX_CACHED_LOGINS) { + + DEBUG(10,("need to delete the oldest cached login\n")); + + status = wcache_remove_oldest_cached_creds(domain, &cred_sid); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10,("failed to remove oldest cached cred: %s\n", + nt_errstr(status))); + return status; + } + } + + E_md4hash(pass, nt_pass); + + dump_data(100, (const char *)nt_pass, NT_HASH_LEN); + + status = wcache_save_creds(domain, mem_ctx, &cred_sid, nt_pass); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + if (info3 != NULL && user != NULL) { + if (!netsamlogon_cache_store(user, info3)) { + return NT_STATUS_ACCESS_DENIED; + } + } + + return NT_STATUS_OK; +} + +NTSTATUS winbindd_update_creds_by_info3(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const char *user, + const char *pass, + NET_USER_INFO_3 *info3) +{ + return winbindd_store_creds(domain, mem_ctx, user, pass, info3, NULL); +} + +NTSTATUS winbindd_update_creds_by_sid(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const DOM_SID *sid, + const char *pass) +{ + return winbindd_store_creds(domain, mem_ctx, NULL, pass, NULL, sid); +} + +NTSTATUS winbindd_update_creds_by_name(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + const char *user, + const char *pass) +{ + return winbindd_store_creds(domain, mem_ctx, user, pass, NULL, NULL); +} + + diff --git a/source3/nsswitch/winbindd_dual.c b/source3/nsswitch/winbindd_dual.c index 38030ee9ac..1988f36b51 100644 --- a/source3/nsswitch/winbindd_dual.c +++ b/source3/nsswitch/winbindd_dual.c @@ -197,6 +197,8 @@ static void async_reply_recv(void *private_data, BOOL success) SMB_ASSERT(cache_retrieve_response(child->pid, state->response)); + cache_cleanup_response(child->pid); + DLIST_REMOVE(child->requests, state); schedule_async_request(child); @@ -233,6 +235,8 @@ static void schedule_async_request(struct winbindd_child *child) setup_async_write(&child->event, request->request, sizeof(*request->request), async_main_request_sent, request); + + talloc_destroy(child->mem_ctx); return; } @@ -347,6 +351,7 @@ static struct winbindd_child_dispatch_table child_dispatch_table[] = { { WINBINDD_SHOW_SEQUENCE, winbindd_dual_show_sequence, "SHOW_SEQUENCE" }, { WINBINDD_PAM_AUTH, winbindd_dual_pam_auth, "PAM_AUTH" }, { WINBINDD_PAM_AUTH_CRAP, winbindd_dual_pam_auth_crap, "AUTH_CRAP" }, + { WINBINDD_PAM_LOGOFF, winbindd_dual_pam_logoff, "PAM_LOGOFF" }, { WINBINDD_CHECK_MACHACC, winbindd_dual_check_machine_acct, "CHECK_MACHACC" }, { WINBINDD_DUAL_SID2UID, winbindd_dual_sid2uid, "DUAL_SID2UID" }, { WINBINDD_DUAL_SID2GID, winbindd_dual_sid2gid, "DUAL_SID2GID" }, @@ -356,8 +361,8 @@ static struct winbindd_child_dispatch_table child_dispatch_table[] = { { WINBINDD_DUAL_NAME2GID, winbindd_dual_name2gid, "DUAL_NAME2GID" }, { WINBINDD_DUAL_IDMAPSET, winbindd_dual_idmapset, "DUAL_IDMAPSET" }, { WINBINDD_DUAL_USERINFO, winbindd_dual_userinfo, "DUAL_USERINFO" }, - { WINBINDD_ALLOCATE_RID, winbindd_dual_allocate_rid, "ALLOCATE_RID" }, - { WINBINDD_ALLOCATE_RID_AND_GID, winbindd_dual_allocate_rid_and_gid, "ALLOCATE_RID_AND_GID" }, + { WINBINDD_ALLOCATE_UID, winbindd_dual_allocate_uid, "ALLOCATE_UID" }, + { WINBINDD_ALLOCATE_GID, winbindd_dual_allocate_gid, "ALLOCATE_GID" }, { WINBINDD_GETUSERDOMGROUPS, winbindd_dual_getuserdomgroups, "GETUSERDOMGROUPS" }, { WINBINDD_DUAL_GETSIDALIASES, winbindd_dual_getsidaliases, "GETSIDALIASES" }, /* End of list */ @@ -444,6 +449,137 @@ void winbind_child_died(pid_t pid) schedule_async_request(child); } +/* Forward the online/offline messages to our children. */ +void winbind_msg_offline(int msg_type, struct process_id src, void *buf, size_t len) +{ + struct winbindd_child *child; + + DEBUG(10,("winbind_msg_offline: got offline message.\n")); + + if (!lp_winbind_offline_logon()) { + DEBUG(10,("winbind_msg_offline: rejecting offline message.\n")); + return; + } + + /* Set our global state as offline. */ + if (!set_global_winbindd_state_offline()) { + DEBUG(10,("winbind_msg_offline: offline request failed.\n")); + return; + } + + for (child = children; child != NULL; child = child->next) { + DEBUG(10,("winbind_msg_offline: sending message to pid %u.\n", + (unsigned int)child->pid )); + message_send_pid(pid_to_procid(child->pid), MSG_WINBIND_OFFLINE, NULL, 0, False); + } +} + +/* Forward the online/offline messages to our children. */ +void winbind_msg_online(int msg_type, struct process_id src, void *buf, size_t len) +{ + struct winbindd_child *child; + + DEBUG(10,("winbind_msg_online: got online message.\n")); + + if (!lp_winbind_offline_logon()) { + DEBUG(10,("winbind_msg_online: rejecting online message.\n")); + return; + } + + /* Set our global state as online. */ + set_global_winbindd_state_online(); + + for (child = children; child != NULL; child = child->next) { + DEBUG(10,("winbind_msg_online: sending message to pid %u.\n", + (unsigned int)child->pid )); + message_send_pid(pid_to_procid(child->pid), MSG_WINBIND_ONLINE, NULL, 0, False); + } +} + +static void account_lockout_policy_handler(struct timed_event *te, + const struct timeval *now, + void *private_data) +{ + struct winbindd_child *child = private_data; + + struct winbindd_methods *methods; + SAM_UNK_INFO_12 lockout_policy; + NTSTATUS result; + + DEBUG(10,("account_lockout_policy_handler called\n")); + + if (child->timed_event) { + talloc_free(child->timed_event); + } + + methods = child->domain->methods; + + result = methods->lockout_policy(child->domain, child->mem_ctx, &lockout_policy); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10,("account_lockout_policy_handler: failed to call lockout_policy\n")); + return; + } + + child->timed_event = add_timed_event(child->mem_ctx, + timeval_current_ofs(3600, 0), + "account_lockout_policy_handler", + account_lockout_policy_handler, + child); +} + +/* Deal with a request to go offline. */ + +static void child_msg_offline(int msg_type, struct process_id src, void *buf, size_t len) +{ + struct winbindd_domain *domain; + + DEBUG(5,("child_msg_offline received.\n")); + + if (!lp_winbind_offline_logon()) { + DEBUG(10,("child_msg_offline: rejecting offline message.\n")); + return; + } + + /* Set our global state as offline. */ + if (!set_global_winbindd_state_offline()) { + DEBUG(10,("child_msg_offline: offline request failed.\n")); + return; + } + + /* Mark all our domains as offline. */ + + for (domain = domain_list(); domain; domain = domain->next) { + DEBUG(5,("child_msg_offline: marking %s offline.\n", domain->name)); + domain->online = False; + } +} + +/* Deal with a request to go online. */ + +static void child_msg_online(int msg_type, struct process_id src, void *buf, size_t len) +{ + struct winbindd_domain *domain; + + DEBUG(5,("child_msg_online received.\n")); + + if (!lp_winbind_offline_logon()) { + DEBUG(10,("child_msg_online: rejecting online message.\n")); + return; + } + + /* Set our global state as online. */ + set_global_winbindd_state_online(); + + /* Mark everything online - delete any negative cache entries + to force an immediate reconnect. */ + + for (domain = domain_list(); domain; domain = domain->next) { + DEBUG(5,("child_msg_online: marking %s online.\n", domain->name)); + domain->online = True; + check_negative_conn_cache_timeout(domain->name, domain->dcname, 0); + } +} + static BOOL fork_domain_child(struct winbindd_child *child) { int fdpair[2]; @@ -459,10 +595,15 @@ static BOOL fork_domain_child(struct winbindd_child *child) ZERO_STRUCT(state); state.pid = getpid(); + /* Ensure we don't process messages whilst we're + changing the disposition for the child. */ + message_block(); + child->pid = sys_fork(); if (child->pid == -1) { DEBUG(0, ("Could not fork: %s\n", strerror(errno))); + message_unblock(); return False; } @@ -475,6 +616,8 @@ static BOOL fork_domain_child(struct winbindd_child *child) child->event.flags = 0; child->requests = NULL; add_fd_event(&child->event); + /* We're ok with online/offline messages now. */ + message_unblock(); return True; } @@ -495,12 +638,80 @@ static BOOL fork_domain_child(struct winbindd_child *child) lp_set_logfile(child->logfilename); reopen_logs(); } - + + /* Don't handle the same messages as our parent. */ + message_deregister(MSG_SMB_CONF_UPDATED); + message_deregister(MSG_SHUTDOWN); + message_deregister(MSG_WINBIND_OFFLINE); + message_deregister(MSG_WINBIND_ONLINE); + + /* The child is ok with online/offline messages now. */ + message_unblock(); + + child->mem_ctx = talloc_init("child_mem_ctx"); + if (child->mem_ctx == NULL) { + return False; + } + + if (child->domain != NULL) { + /* We might be in the idmap child...*/ + child->timed_event = add_timed_event( + child->mem_ctx, timeval_zero(), + "account_lockout_policy_handler", + account_lockout_policy_handler, + child); + } + + /* Handle online/offline messages. */ + message_register(MSG_WINBIND_OFFLINE,child_msg_offline); + message_register(MSG_WINBIND_ONLINE,child_msg_online); + while (1) { + + int ret; + fd_set read_fds; + struct timeval t; + struct timeval *tp; + struct timeval now; + /* free up any talloc memory */ lp_talloc_free(); main_loop_talloc_free(); + run_events(); + + GetTimeOfDay(&now); + + tp = get_timed_events_timeout(&t, (time_t)-1); + if (tp) { + DEBUG(11,("select will use timeout of %d seconds\n", (int)tp->tv_sec)); + } + + /* Handle messages */ + + message_dispatch(); + + FD_ZERO(&read_fds); + FD_SET(state.sock, &read_fds); + + ret = sys_select(state.sock + 1, &read_fds, NULL, NULL, tp); + + if (ret == 0) { + DEBUG(10,("nothing is ready yet, continue\n")); + continue; + } + + if (ret == -1 && errno == EINTR) { + /* We got a signal - continue. */ + continue; + } + + if (ret == -1 && errno != EINTR) { + DEBUG(0,("select error occured\n")); + perror("select"); + return False; + } + /* fetch a request from the main daemon */ child_read_request(&state); diff --git a/source3/nsswitch/winbindd_group.c b/source3/nsswitch/winbindd_group.c index ff2d19f5fc..a328cebac4 100644 --- a/source3/nsswitch/winbindd_group.c +++ b/source3/nsswitch/winbindd_group.c @@ -140,7 +140,7 @@ static BOOL fill_grent_mem(struct winbindd_domain *domain, /* make sure to allow machine accounts */ if (name_types[i] != SID_NAME_USER && name_types[i] != SID_NAME_COMPUTER) { - DEBUG(3, ("name %s isn't a domain user\n", the_name)); + DEBUG(3, ("name %s isn't a domain user (%s)\n", the_name, sid_type_lookup(name_types[i]))); continue; } @@ -208,6 +208,8 @@ void winbindd_getgrnam(struct winbindd_cli_state *state) char *tmp, *gr_mem; size_t gr_mem_len; gid_t gid; + union unid_t id; + NTSTATUS status; /* Ensure null termination */ state->request.data.groupname[sizeof(state->request.data.groupname)-1]='\0'; @@ -241,8 +243,8 @@ void winbindd_getgrnam(struct winbindd_cli_state *state) /* should we deal with users for our domain? */ if ( lp_winbind_trusted_domains_only() && domain->primary) { - DEBUG(7,("winbindd_getgrnam: My domain -- rejecting getgrnam() for %s\\%s.\n", - name_domain, name_group)); + DEBUG(7,("winbindd_getgrnam: My domain -- rejecting " + "getgrnam() for %s\\%s.\n", name_domain, name_group)); request_error(state); return; } @@ -262,18 +264,35 @@ void winbindd_getgrnam(struct winbindd_cli_state *state) ((name_type==SID_NAME_ALIAS) && domain->internal) || ((name_type==SID_NAME_WKN_GRP) && domain->internal)) ) { - DEBUG(1, ("name '%s' is not a local, domain or builtin group: %d\n", - name_group, name_type)); + DEBUG(1, ("name '%s' is not a local, domain or builtin " + "group: %d\n", name_group, name_type)); request_error(state); return; } - if (!NT_STATUS_IS_OK(idmap_sid_to_gid(&group_sid, &gid, 0))) { - DEBUG(1, ("error converting unix gid to sid\n")); - request_error(state); - return; + /* Try to get the GID */ + + status = idmap_sid_to_gid(&group_sid, &gid, 0); + + if (NT_STATUS_IS_OK(status)) { + goto got_gid; + } + + /* Maybe it's one of our aliases in passdb */ + + if (pdb_sid_to_id(&group_sid, &id, &name_type) && + ((name_type == SID_NAME_ALIAS) || + (name_type == SID_NAME_WKN_GRP))) { + gid = id.gid; + goto got_gid; } + DEBUG(1, ("error converting unix gid to sid\n")); + request_error(state); + return; + + got_gid: + if (!fill_grent(&state->response.data.gr, name_domain, name_group, gid) || !fill_grent_mem(domain, &group_sid, name_type, @@ -303,6 +322,7 @@ void winbindd_getgrgid(struct winbindd_cli_state *state) fstring group_name; size_t gr_mem_len; char *gr_mem; + NTSTATUS status; DEBUG(3, ("[%5lu]: getgrgid %lu\n", (unsigned long)state->pid, (unsigned long)state->request.data.gid)); @@ -315,14 +335,29 @@ void winbindd_getgrgid(struct winbindd_cli_state *state) return; } - /* Get rid from gid */ - if (!NT_STATUS_IS_OK(idmap_gid_to_sid(&group_sid, state->request.data.gid, 0))) { - DEBUG(1, ("could not convert gid %lu to rid\n", - (unsigned long)state->request.data.gid)); - request_error(state); - return; + /* Get sid from gid */ + + status = idmap_gid_to_sid(&group_sid, state->request.data.gid, 0); + if (NT_STATUS_IS_OK(status)) { + /* This is a remote one */ + goto got_sid; } + /* Ok, this might be "ours", i.e. an alias */ + + if (pdb_gid_to_sid(state->request.data.gid, &group_sid) && + lookup_sid(state->mem_ctx, &group_sid, NULL, NULL, &name_type) && + (name_type == SID_NAME_ALIAS)) { + /* Hey, got an alias */ + goto got_sid; + } + + DEBUG(1, ("could not convert gid %lu to sid\n", + (unsigned long)state->request.data.gid)); + request_error(state); + return; + + got_sid: /* Get name from sid */ if (!winbindd_lookup_name_by_sid(state->mem_ctx, &group_sid, dom_name, @@ -665,13 +700,32 @@ void winbindd_getgrent(struct winbindd_cli_state *state) sid_copy(&group_sid, &domain->sid); sid_append_rid(&group_sid, name_list[ent->sam_entry_index].rid); - if (!NT_STATUS_IS_OK(idmap_sid_to_gid(&group_sid, &group_gid, 0))) { - - DEBUG(1, ("could not look up gid for group %s\n", - name_list[ent->sam_entry_index].acct_name)); - - ent->sam_entry_index++; - goto tryagain; + if (!NT_STATUS_IS_OK(idmap_sid_to_gid(&group_sid, + &group_gid, 0))) { + union unid_t id; + enum SID_NAME_USE type; + + DEBUG(10, ("SID %s not in idmap\n", + sid_string_static(&group_sid))); + + if (!pdb_sid_to_id(&group_sid, &id, &type)) { + DEBUG(1, ("could not look up gid for group " + "%s\n", + name_list[ent->sam_entry_index].acct_name)); + ent->sam_entry_index++; + goto tryagain; + } + + if ((type != SID_NAME_DOM_GRP) && + (type != SID_NAME_ALIAS) && + (type != SID_NAME_WKN_GRP)) { + DEBUG(1, ("Group %s is a %s, not a group\n", + sid_type_lookup(type), + name_list[ent->sam_entry_index].acct_name)); + ent->sam_entry_index++; + goto tryagain; + } + group_gid = id.gid; } DEBUG(10, ("got gid %lu for group %lu\n", (unsigned long)group_gid, @@ -1187,4 +1241,3 @@ enum winbindd_result winbindd_dual_getuserdomgroups(struct winbindd_domain *doma return WINBINDD_OK; } - diff --git a/source3/nsswitch/winbindd_misc.c b/source3/nsswitch/winbindd_misc.c index 1fbf4b33df..b6aecae393 100644 --- a/source3/nsswitch/winbindd_misc.c +++ b/source3/nsswitch/winbindd_misc.c @@ -115,6 +115,7 @@ enum winbindd_result winbindd_dual_list_trusted_domains(struct winbindd_domain * int extra_data_len = 0; char *extra_data; NTSTATUS result; + BOOL have_own_domain = False; DEBUG(3, ("[%5lu]: list trusted domains\n", (unsigned long)state->pid)); @@ -137,6 +138,22 @@ enum winbindd_result winbindd_dual_list_trusted_domains(struct winbindd_domain * names[i], alt_names[i] ? alt_names[i] : names[i], sid_string_static(&sids[i])); + /* add our primary domain */ + + for (i=0; iname)) { + have_own_domain = True; + break; + } + } + + if (state->request.data.list_all_domains && !have_own_domain) { + extra_data = talloc_asprintf(state->mem_ctx, "%s\n%s\\%s\\%s", + extra_data, + domain->name, + domain->alt_name ? domain->alt_name : domain->name, + sid_string_static(&domain->sid)); + } /* This is a bit excessive, but the extra data sooner or later will be talloc'ed */ diff --git a/source3/nsswitch/winbindd_nss.h b/source3/nsswitch/winbindd_nss.h index eda68ae5c7..033e51d794 100644 --- a/source3/nsswitch/winbindd_nss.h +++ b/source3/nsswitch/winbindd_nss.h @@ -34,7 +34,7 @@ /* Update this when you change the interface. */ -#define WINBIND_INTERFACE_VERSION 11 +#define WINBIND_INTERFACE_VERSION 14 /* Socket commands */ @@ -64,6 +64,7 @@ enum winbindd_cmd { WINBINDD_PAM_AUTH, WINBINDD_PAM_AUTH_CRAP, WINBINDD_PAM_CHAUTHTOK, + WINBINDD_PAM_LOGOFF, /* List various things */ @@ -82,8 +83,9 @@ enum winbindd_cmd { WINBINDD_SID_TO_GID, WINBINDD_UID_TO_SID, WINBINDD_GID_TO_SID, - WINBINDD_ALLOCATE_RID, - WINBINDD_ALLOCATE_RID_AND_GID, + + WINBINDD_ALLOCATE_UID, + WINBINDD_ALLOCATE_GID, /* Miscellaneous other stuff */ @@ -114,7 +116,7 @@ enum winbindd_cmd { /* return a list of group sids for a user sid */ WINBINDD_GETUSERSIDS, - /* Return the domain groups a user is in */ + /* Various group queries */ WINBINDD_GETUSERDOMGROUPS, /* Initialize connection in a child */ @@ -165,7 +167,6 @@ typedef struct winbindd_gr { #define WBFLAG_PAM_LMKEY 0x0008 #define WBFLAG_PAM_CONTACT_TRUSTDOM 0x0010 #define WBFLAG_QUERY_ONLY 0x0020 -#define WBFLAG_ALLOCATE_RID 0x0040 #define WBFLAG_PAM_UNIX_NAME 0x0080 #define WBFLAG_PAM_AFS_TOKEN 0x0100 #define WBFLAG_PAM_NT_STATUS_SQUASH 0x0200 @@ -175,6 +176,10 @@ typedef struct winbindd_gr { /* Flag to say this is a winbindd internal send - don't recurse. */ #define WBFLAG_RECURSE 0x0800 +#define WBFLAG_PAM_KRB5 0x1000 +#define WBFLAG_PAM_FALLBACK_AFTER_KRB5 0x2000 +#define WBFLAG_PAM_CACHED_LOGIN 0x4000 + #define WINBINDD_MAX_EXTRA_DATA (128*1024) /* Winbind request structure */ @@ -199,6 +204,8 @@ struct winbindd_request { fstring user; fstring pass; fstring require_membership_of_sid; + fstring krb5_cc_type; + uid_t uid; } auth; /* pam_winbind auth module */ struct { unsigned char chal[8]; @@ -217,6 +224,11 @@ struct winbindd_request { fstring oldpass; fstring newpass; } chauthtok; /* pam_winbind passwd module */ + struct { + fstring user; + fstring krb5ccname; + uid_t uid; + } logoff; /* pam_winbind session module */ fstring sid; /* lookupsid, sid_to_[ug]id */ struct { fstring dom_name; /* lookupname */ @@ -242,6 +254,7 @@ struct winbindd_request { gid_t gid; fstring sid; } dual_idmapset; + BOOL list_all_domains; } data; char *extra_data; size_t extra_len; @@ -307,12 +320,41 @@ struct winbindd_response { int pam_error; char user_session_key[16]; char first_8_lm_hash[8]; + fstring krb5ccname; + struct policy_settings { + uint16 min_length_password; + uint16 password_history; + uint32 password_properties; + time_t expire; + time_t min_passwordage; + } policy; + uint32 reject_reason; + struct info3_text { + time_t logon_time; + time_t logoff_time; + time_t kickoff_time; + time_t pass_last_set_time; + time_t pass_can_change_time; + time_t pass_must_change_time; + uint16 logon_count; + uint16 bad_pw_count; + fstring user_sid; + fstring group_sid; + fstring dom_sid; + uint32 num_groups; + uint32 user_flgs; + uint32 acct_flags; + uint32 num_other_sids; + fstring user_name; + fstring full_name; + fstring logon_script; + fstring profile_path; + fstring home_dir; + fstring dir_drive; + fstring logon_srv; + fstring logon_dom; + } info3; } auth; - uint32 rid; /* create user or group or allocate rid */ - struct { - uint32 rid; - gid_t gid; - } rid_and_gid; struct { fstring name; fstring alt_name; @@ -336,4 +378,20 @@ struct winbindd_response { void *extra_data; /* getgrnam, getgrgid, getgrent */ }; +struct WINBINDD_CCACHE_ENTRY { + const char *principal_name; + const char *ccname; + const char *service; + const char *username; + const char *sid_string; + const char *pass; + uid_t uid; + time_t create_time; + time_t renew_until; + BOOL refresh_tgt; + time_t refresh_time; + struct timed_event *event; + struct WINBINDD_CCACHE_ENTRY *next, *prev; +}; + #endif diff --git a/source3/nsswitch/winbindd_pam.c b/source3/nsswitch/winbindd_pam.c index 890007ae38..ab20102f79 100644 --- a/source3/nsswitch/winbindd_pam.c +++ b/source3/nsswitch/winbindd_pam.c @@ -6,6 +6,7 @@ Copyright (C) Andrew Tridgell 2000 Copyright (C) Tim Potter 2001 Copyright (C) Andrew Bartlett 2001-2002 + Copyright (C) Guenther Deschner 2005 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 @@ -27,6 +28,70 @@ #undef DBGC_CLASS #define DBGC_CLASS DBGC_WINBIND +static NTSTATUS append_info3_as_txt(TALLOC_CTX *mem_ctx, + struct winbindd_cli_state *state, + NET_USER_INFO_3 *info3) +{ + DOM_SID user_sid, group_sid; + fstring str_sid; + + state->response.data.auth.info3.logon_time = + nt_time_to_unix(&(info3->logon_time)); + state->response.data.auth.info3.logoff_time = + nt_time_to_unix(&(info3->logoff_time)); + state->response.data.auth.info3.kickoff_time = + nt_time_to_unix(&(info3->kickoff_time)); + state->response.data.auth.info3.pass_last_set_time = + nt_time_to_unix(&(info3->pass_last_set_time)); + state->response.data.auth.info3.pass_can_change_time = + nt_time_to_unix(&(info3->pass_can_change_time)); + state->response.data.auth.info3.pass_must_change_time = + nt_time_to_unix(&(info3->pass_must_change_time)); + + state->response.data.auth.info3.logon_count = info3->logon_count; + state->response.data.auth.info3.bad_pw_count = info3->bad_pw_count; + + sid_copy(&user_sid, &(info3->dom_sid.sid)); + sid_append_rid(&user_sid, info3->user_rid); + + sid_to_string(str_sid, &user_sid); + fstrcpy(state->response.data.auth.info3.user_sid, str_sid); + + sid_copy(&group_sid, &(info3->dom_sid.sid)); + sid_append_rid(&group_sid, info3->group_rid); + + sid_to_string(str_sid, &group_sid); + fstrcpy(state->response.data.auth.info3.group_sid, str_sid); + + sid_to_string(str_sid, &(info3->dom_sid.sid)); + fstrcpy(state->response.data.auth.info3.dom_sid, str_sid); + + state->response.data.auth.info3.num_groups = info3->num_groups; + state->response.data.auth.info3.user_flgs = info3->user_flgs; + + state->response.data.auth.info3.acct_flags = info3->acct_flags; + state->response.data.auth.info3.num_other_sids = info3->num_other_sids; + + unistr2_to_ascii(state->response.data.auth.info3.user_name, + &info3->uni_user_name, -1); + unistr2_to_ascii(state->response.data.auth.info3.full_name, + &info3->uni_full_name, -1); + unistr2_to_ascii(state->response.data.auth.info3.logon_script, + &info3->uni_logon_script, -1); + unistr2_to_ascii(state->response.data.auth.info3.profile_path, + &info3->uni_profile_path, -1); + unistr2_to_ascii(state->response.data.auth.info3.home_dir, + &info3->uni_home_dir, -1); + unistr2_to_ascii(state->response.data.auth.info3.dir_drive, + &info3->uni_dir_drive, -1); + + unistr2_to_ascii(state->response.data.auth.info3.logon_srv, + &info3->uni_logon_srv, -1); + unistr2_to_ascii(state->response.data.auth.info3.logon_dom, + &info3->uni_logon_dom, -1); + + return NT_STATUS_OK; +} static NTSTATUS append_info3_as_ndr(TALLOC_CTX *mem_ctx, struct winbindd_cli_state *state, @@ -145,14 +210,15 @@ static NTSTATUS check_info3_in_group(TALLOC_CTX *mem_ctx, return NT_STATUS_LOGON_FAILURE; } -static struct winbindd_domain *find_auth_domain(const char *domain_name) +static struct winbindd_domain *find_auth_domain(struct winbindd_cli_state *state, + const char *domain_name) { struct winbindd_domain *domain; if (IS_DC) { domain = find_domain_from_name_noinit(domain_name); if (domain == NULL) { - DEBUG(3, ("Authentication for domain [%s] " + DEBUG(3, ("Authentication for domain [%s] refused" "as it is not a trusted domain\n", domain_name)); } @@ -166,6 +232,18 @@ static struct winbindd_domain *find_auth_domain(const char *domain_name) return NULL; } + /* we can auth against trusted domains */ + if (state->request.flags & WBFLAG_PAM_CONTACT_TRUSTDOM) { + domain = find_domain_from_name_noinit(domain_name); + if (domain == NULL) { + DEBUG(3, ("Authentication for domain [%s] skipped " + "as it is not a trusted domain\n", + domain_name)); + } else { + return domain; + } + } + return find_our_domain(); } @@ -181,9 +259,372 @@ static void set_auth_errors(struct winbindd_response *resp, NTSTATUS result) resp->data.auth.pam_error = nt_status_to_pam(result); } +static NTSTATUS fillup_password_policy(struct winbindd_domain *domain, + struct winbindd_cli_state *state) +{ + struct winbindd_methods *methods; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + SAM_UNK_INFO_1 password_policy; + + methods = domain->methods; + + status = methods->password_policy(domain, state->mem_ctx, &password_policy); + if (NT_STATUS_IS_ERR(status)) { + return status; + } + + state->response.data.auth.policy.min_length_password = + password_policy.min_length_password; + state->response.data.auth.policy.password_history = + password_policy.password_history; + state->response.data.auth.policy.password_properties = + password_policy.password_properties; + state->response.data.auth.policy.expire = + nt_time_to_unix_abs(&(password_policy.expire)); + state->response.data.auth.policy.min_passwordage = + nt_time_to_unix_abs(&(password_policy.min_passwordage)); + + return NT_STATUS_OK; +} + +static NTSTATUS get_max_bad_attempts_from_lockout_policy(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + uint16 *max_allowed_bad_attempts) +{ + struct winbindd_methods *methods; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + SAM_UNK_INFO_12 lockout_policy; + + *max_allowed_bad_attempts = 0; + + methods = domain->methods; + + status = methods->lockout_policy(domain, mem_ctx, &lockout_policy); + if (NT_STATUS_IS_ERR(status)) { + return status; + } + + *max_allowed_bad_attempts = lockout_policy.bad_attempt_lockout; + + return NT_STATUS_OK; +} + + +static const char *generate_krb5_ccache(TALLOC_CTX *mem_ctx, + const char *type, + uid_t uid, + BOOL *internal_ccache) +{ + /* accept KCM, FILE and WRFILE as krb5_cc_type from the client and then + * build the full ccname string based on the user's uid here - + * Guenther*/ + + const char *gen_cc = NULL; + + *internal_ccache = True; + + if (uid == -1) { + goto memory_ccache; + } + + if (!type || type[0] == '\0') { + goto memory_ccache; + } + + if (strequal(type, "FILE")) { + gen_cc = talloc_asprintf(mem_ctx, "FILE:/tmp/krb5cc_%d", uid); + } else if (strequal(type, "WRFILE")) { + gen_cc = talloc_asprintf(mem_ctx, "WRFILE:/tmp/krb5cc_%d", uid); +#ifdef WITH_KCM + } else if (strequal(type, "KCM")) { + gen_cc = talloc_asprintf(mem_ctx, "KCM:%d", uid); +#endif + } else { + DEBUG(10,("we don't allow to set a %s type ccache\n", type)); + goto memory_ccache; + } + + *internal_ccache = False; + goto done; + + memory_ccache: + gen_cc = talloc_strdup(mem_ctx, "MEMORY:winbind_cache"); + + done: + if (gen_cc == NULL) { + DEBUG(0,("out of memory\n")); + return NULL; + } + + DEBUG(10,("using ccache: %s %s\n", gen_cc, *internal_ccache ? "(internal)":"")); + + return gen_cc; +} + +static uid_t get_uid_from_state(struct winbindd_cli_state *state) +{ + uid_t uid = -1; + + uid = state->request.data.auth.uid; + + if (uid < 0) { + DEBUG(1,("invalid uid: '%d'\n", uid)); + return -1; + } + return uid; +} + +static void setup_return_cc_name(struct winbindd_cli_state *state, const char *cc) +{ + const char *type = state->request.data.auth.krb5_cc_type; + + state->response.data.auth.krb5ccname[0] = '\0'; + + if (type[0] == '\0') { + return; + } + + if (!strequal(type, "FILE") && +#ifdef WITH_KCM + !strequal(type, "KCM") && +#endif + !strequal(type, "WRFILE")) { + DEBUG(10,("won't return krbccname for a %s type ccache\n", + type)); + return; + } + + fstrcpy(state->response.data.auth.krb5ccname, cc); +} + /********************************************************************** - Authenticate a user with a clear text password -**********************************************************************/ + Authenticate a user with a clear text password using Kerberos and fill up + ccache if required + **********************************************************************/ +static NTSTATUS winbindd_raw_kerberos_login(struct winbindd_domain *domain, + struct winbindd_cli_state *state, + NET_USER_INFO_3 **info3) +{ +#ifdef HAVE_KRB5 + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + krb5_error_code krb5_ret; + DATA_BLOB tkt, session_key_krb5; + DATA_BLOB ap_rep, session_key; + PAC_DATA *pac_data = NULL; + PAC_LOGON_INFO *logon_info = NULL; + char *client_princ = NULL; + char *client_princ_out = NULL; + char *local_service = NULL; + const char *cc = NULL; + const char *principal_s = NULL; + const char *service = NULL; + char *realm = NULL; + fstring name_domain, name_user; + time_t ticket_lifetime = 0; + time_t renewal_until = 0; + uid_t uid = -1; + ADS_STRUCT *ads; + time_t time_offset = 0; + BOOL internal_ccache = True; + + ZERO_STRUCT(session_key); + ZERO_STRUCT(session_key_krb5); + ZERO_STRUCT(tkt); + ZERO_STRUCT(ap_rep); + + ZERO_STRUCTP(info3); + + *info3 = NULL; + + /* 1st step: + * prepare a krb5_cc_cache string for the user */ + + uid = get_uid_from_state(state); + if (uid == -1) { + DEBUG(0,("no valid uid\n")); + } + + cc = generate_krb5_ccache(state->mem_ctx, + state->request.data.auth.krb5_cc_type, + state->request.data.auth.uid, + &internal_ccache); + if (cc == NULL) { + return NT_STATUS_NO_MEMORY; + } + + + /* 2nd step: + * get kerberos properties */ + + if (domain->private_data) { + ads = (ADS_STRUCT *)domain->private_data; + time_offset = ads->auth.time_offset; + } + + + /* 3rd step: + * do kerberos auth and setup ccache as the user */ + + parse_domain_user(state->request.data.auth.user, name_domain, name_user); + + realm = domain->alt_name; + strupper_m(realm); + + principal_s = talloc_asprintf(state->mem_ctx, "%s@%s", name_user, realm); + if (principal_s == NULL) { + return NT_STATUS_NO_MEMORY; + } + + service = talloc_asprintf(state->mem_ctx, "krbtgt/%s@%s", realm, realm); + if (service == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* if this is a user ccache, we need to act as the user to let the krb5 + * library handle the chown, etc. */ + + /************************ NON-ROOT **********************/ + + if (!internal_ccache) { + + seteuid(uid); + DEBUG(10,("winbindd_raw_kerberos_login: uid is %d\n", uid)); + } + + krb5_ret = kerberos_kinit_password(principal_s, + state->request.data.auth.pass, + time_offset, + &ticket_lifetime, + &renewal_until, + cc, + True, + WINBINDD_PAM_AUTH_KRB5_RENEW_TIME); + + if (krb5_ret) { + DEBUG(1,("winbindd_raw_kerberos_login: kinit failed for '%s' with: %s (%d)\n", + principal_s, error_message(krb5_ret), krb5_ret)); + result = krb5_to_nt_status(krb5_ret); + goto done; + } + + /* does http_timestring use heimdals libroken strftime?? - Guenther */ + DEBUG(10,("got TGT for %s in %s (valid until: %s (%d), renewable till: %s (%d))\n", + principal_s, cc, + http_timestring(ticket_lifetime), (int)ticket_lifetime, + http_timestring(renewal_until), (int)renewal_until)); + + client_princ = talloc_strdup(state->mem_ctx, global_myname()); + if (client_princ == NULL) { + result = NT_STATUS_NO_MEMORY; + goto done; + } + strlower_m(client_princ); + + local_service = talloc_asprintf(state->mem_ctx, "HOST/%s@%s", client_princ, lp_realm()); + if (local_service == NULL) { + DEBUG(0,("winbindd_raw_kerberos_login: out of memory\n")); + result = NT_STATUS_NO_MEMORY; + goto done; + } + + krb5_ret = cli_krb5_get_ticket(local_service, + time_offset, + &tkt, + &session_key_krb5, + 0, + cc); + if (krb5_ret) { + DEBUG(1,("winbindd_raw_kerberos_login: failed to get ticket for: %s\n", + local_service)); + result = krb5_to_nt_status(krb5_ret); + goto done; + } + + if (!internal_ccache) { + seteuid(0); + } + + /************************ NON-ROOT **********************/ + + result = ads_verify_ticket(state->mem_ctx, + lp_realm(), + &tkt, + &client_princ_out, + &pac_data, + &ap_rep, + &session_key); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(0,("winbindd_raw_kerberos_login: ads_verify_ticket failed: %s\n", + nt_errstr(result))); + goto done; + } + + DEBUG(10,("winbindd_raw_kerberos_login: winbindd validated ticket of %s\n", + client_princ)); + + if (!pac_data) { + DEBUG(3,("winbindd_raw_kerberos_login: no pac data\n")); + result = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + logon_info = get_logon_info_from_pac(pac_data); + if (logon_info == NULL) { + DEBUG(1,("winbindd_raw_kerberos_login: no logon info\n")); + result = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + + /* last step: + * put results together */ + + *info3 = &logon_info->info3; + + /* if we had a user's ccache then return that string for the pam + * environment */ + + if (!internal_ccache) { + + setup_return_cc_name(state, cc); + + result = add_ccache_to_list(principal_s, + cc, + service, + state->request.data.auth.user, + NULL, + state->request.data.auth.pass, + uid, + time(NULL), + ticket_lifetime, + renewal_until, + lp_winbind_refresh_tickets()); + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10,("winbindd_raw_kerberos_login: failed to add ccache to list: %s\n", + nt_errstr(result))); + } + } + + result = NT_STATUS_OK; + +done: + data_blob_free(&session_key); + data_blob_free(&session_key_krb5); + data_blob_free(&ap_rep); + data_blob_free(&tkt); + + SAFE_FREE(client_princ_out); + + if (!internal_ccache) { + seteuid(0); + } + + return result; +#else + return NT_STATUS_NOT_SUPPORTED; +#endif /* HAVE_KRB5 */ +} void winbindd_pam_auth(struct winbindd_cli_state *state) { @@ -206,7 +647,7 @@ void winbindd_pam_auth(struct winbindd_cli_state *state) parse_domain_user(state->request.data.auth.user, name_domain, name_user); - domain = find_auth_domain(name_domain); + domain = find_auth_domain(state, name_domain); if (domain == NULL) { set_auth_errors(&state->response, NT_STATUS_NO_SUCH_USER); @@ -222,12 +663,221 @@ void winbindd_pam_auth(struct winbindd_cli_state *state) sendto_domain(state, domain); } -enum winbindd_result winbindd_dual_pam_auth(struct winbindd_domain *domain, - struct winbindd_cli_state *state) +NTSTATUS winbindd_dual_pam_auth_cached(struct winbindd_domain *domain, + struct winbindd_cli_state *state, + NET_USER_INFO_3 **info3) { - NTSTATUS result; + NTSTATUS result = NT_STATUS_LOGON_FAILURE; + uint16 max_allowed_bad_attempts; fstring name_domain, name_user; - NET_USER_INFO_3 info3; + DOM_SID sid; + enum SID_NAME_USE type; + uchar new_nt_pass[NT_HASH_LEN]; + const uint8 *cached_nt_pass; + NET_USER_INFO_3 *my_info3; + time_t kickoff_time, must_change_time; + + *info3 = NULL; + + ZERO_STRUCTP(info3); + + DEBUG(10,("winbindd_dual_pam_auth_cached\n")); + + /* Parse domain and username */ + + parse_domain_user(state->request.data.auth.user, name_domain, name_user); + + + if (!lookup_cached_name(state->mem_ctx, + name_domain, + name_user, + &sid, + &type)) { + DEBUG(10,("winbindd_dual_pam_auth_cached: no such user in the cache\n")); + return NT_STATUS_NO_SUCH_USER; + } + + if (type != SID_NAME_USER) { + DEBUG(10,("winbindd_dual_pam_auth_cached: not a user (%s)\n", sid_type_lookup(type))); + return NT_STATUS_LOGON_FAILURE; + } + + result = winbindd_get_creds(domain, + state->mem_ctx, + &sid, + &my_info3, + &cached_nt_pass); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10,("winbindd_dual_pam_auth_cached: failed to get creds: %s\n", nt_errstr(result))); + return result; + } + + *info3 = my_info3; + + E_md4hash(state->request.data.auth.pass, new_nt_pass); + + dump_data(100, (const char *)new_nt_pass, NT_HASH_LEN); + dump_data(100, (const char *)cached_nt_pass, NT_HASH_LEN); + + if (!memcmp(cached_nt_pass, new_nt_pass, NT_HASH_LEN)) { + + /* User *DOES* know the password, update logon_time and reset + * bad_pw_count */ + + my_info3->user_flgs |= LOGON_CACHED_ACCOUNT; + + if (my_info3->acct_flags & ACB_AUTOLOCK) { + return NT_STATUS_ACCOUNT_LOCKED_OUT; + } + + if (my_info3->acct_flags & ACB_DISABLED) { + return NT_STATUS_ACCOUNT_DISABLED; + } + + if (my_info3->acct_flags & ACB_WSTRUST) { + return NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT; + } + + if (my_info3->acct_flags & ACB_SVRTRUST) { + return NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT; + } + + if (my_info3->acct_flags & ACB_DOMTRUST) { + return NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT; + } + + if (!(my_info3->acct_flags & ACB_NORMAL)) { + DEBUG(10,("winbindd_dual_pam_auth_cached: whats wrong with that one?: 0x%08x\n", + my_info3->acct_flags)); + return NT_STATUS_LOGON_FAILURE; + } + + kickoff_time = nt_time_to_unix(&my_info3->kickoff_time); + if (kickoff_time != 0 && time(NULL) > kickoff_time) { + return NT_STATUS_ACCOUNT_EXPIRED; + } + + must_change_time = nt_time_to_unix(&my_info3->pass_must_change_time); + if (must_change_time != 0 && must_change_time < time(NULL)) { + return NT_STATUS_PASSWORD_EXPIRED; + } + + /* FIXME: we possibly should handle logon hours as well (does xp when + * offline?) see auth/auth_sam.c:sam_account_ok for details */ + + unix_to_nt_time(&my_info3->logon_time, time(NULL)); + my_info3->bad_pw_count = 0; + + result = winbindd_update_creds_by_info3(domain, + state->mem_ctx, + state->request.data.auth.user, + state->request.data.auth.pass, + my_info3); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(1,("failed to update creds: %s\n", nt_errstr(result))); + return result; + } + + return NT_STATUS_OK; + + } + + /* User does *NOT* know the correct password, modify info3 accordingly */ + + /* failure of this is not critical */ + result = get_max_bad_attempts_from_lockout_policy(domain, state->mem_ctx, &max_allowed_bad_attempts); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10,("winbindd_dual_pam_auth_cached: failed to get max_allowed_bad_attempts. " + "Won't be able to honour account lockout policies\n")); + } + + if (max_allowed_bad_attempts == 0) { + return NT_STATUS_WRONG_PASSWORD; + } + + /* increase counter */ + if (my_info3->bad_pw_count < max_allowed_bad_attempts) { + + my_info3->bad_pw_count++; + } + + /* lockout user */ + if (my_info3->bad_pw_count >= max_allowed_bad_attempts) { + + my_info3->acct_flags |= ACB_AUTOLOCK; + } + + result = winbindd_update_creds_by_info3(domain, + state->mem_ctx, + state->request.data.auth.user, + NULL, + my_info3); + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(0,("winbindd_dual_pam_auth_cached: failed to update creds %s\n", + nt_errstr(result))); + } + + return NT_STATUS_LOGON_FAILURE; +} + +NTSTATUS winbindd_dual_pam_auth_kerberos(struct winbindd_domain *domain, + struct winbindd_cli_state *state, + NET_USER_INFO_3 **info3) +{ + struct winbindd_domain *contact_domain; + fstring name_domain, name_user; + NTSTATUS result; + + DEBUG(10,("winbindd_dual_pam_auth_kerberos\n")); + + /* Parse domain and username */ + + parse_domain_user(state->request.data.auth.user, name_domain, name_user); + + /* what domain should we contact? */ + + if ( IS_DC ) { + if (!(contact_domain = find_domain_from_name(name_domain))) { + DEBUG(3, ("Authentication for domain for [%s] -> [%s]\\[%s] failed as %s is not a trusted domain\n", + state->request.data.auth.user, name_domain, name_user, name_domain)); + result = NT_STATUS_NO_SUCH_USER; + goto done; + } + + } else { + if (is_myname(name_domain)) { + DEBUG(3, ("Authentication for domain %s (local domain to this server) not supported at this stage\n", name_domain)); + result = NT_STATUS_NO_SUCH_USER; + goto done; + } + + contact_domain = find_domain_from_name(name_domain); + if (contact_domain == NULL) { + DEBUG(3, ("Authentication for domain for [%s] -> [%s]\\[%s] failed as %s is not a trusted domain\n", + state->request.data.auth.user, name_domain, name_user, name_domain)); + + contact_domain = find_our_domain(); + } + } + + set_dc_type_and_flags(contact_domain); + + if (!contact_domain->active_directory) { + DEBUG(3,("krb5 auth requested but domain is not Active Directory\n")); + return NT_STATUS_INVALID_LOGON_TYPE; + } + + result = winbindd_raw_kerberos_login(contact_domain, state, info3); +done: + return result; +} + +NTSTATUS winbindd_dual_pam_auth_samlogon(struct winbindd_domain *domain, + struct winbindd_cli_state *state, + NET_USER_INFO_3 **info3) +{ + struct rpc_pipe_client *netlogon_pipe; uchar chal[8]; DATA_BLOB lm_resp; @@ -236,17 +886,23 @@ enum winbindd_result winbindd_dual_pam_auth(struct winbindd_domain *domain, unsigned char local_lm_response[24]; unsigned char local_nt_response[24]; struct winbindd_domain *contact_domain; + fstring name_domain, name_user; BOOL retry; + NTSTATUS result; + NET_USER_INFO_3 *my_info3; - /* Ensure null termination */ - state->request.data.auth.user[sizeof(state->request.data.auth.user)-1]='\0'; + ZERO_STRUCTP(info3); - /* Ensure null termination */ - state->request.data.auth.pass[sizeof(state->request.data.auth.pass)-1]='\0'; + *info3 = NULL; - DEBUG(3, ("[%5lu]: pam auth %s\n", (unsigned long)state->pid, - state->request.data.auth.user)); + my_info3 = TALLOC_ZERO_P(state->mem_ctx, NET_USER_INFO_3); + if (my_info3 == NULL) { + return NT_STATUS_NO_MEMORY; + } + + DEBUG(10,("winbindd_dual_pam_auth_samlogon\n")); + /* Parse domain and username */ parse_domain_user(state->request.data.auth.user, name_domain, name_user); @@ -332,7 +988,7 @@ enum winbindd_result winbindd_dual_pam_auth(struct winbindd_domain *domain, do { - ZERO_STRUCT(info3); + ZERO_STRUCTP(my_info3); retry = False; result = cm_connect_netlogon(contact_domain, &netlogon_pipe); @@ -352,7 +1008,7 @@ enum winbindd_result winbindd_dual_pam_auth(struct winbindd_domain *domain, chal, lm_resp, nt_resp, - &info3); + my_info3); attempts += 1; /* We have to try a second time as cm_connect_netlogon @@ -381,25 +1037,154 @@ enum winbindd_result winbindd_dual_pam_auth(struct winbindd_domain *domain, } while ( (attempts < 2) && retry ); + *info3 = my_info3; +done: + return result; +} + +enum winbindd_result winbindd_dual_pam_auth(struct winbindd_domain *domain, + struct winbindd_cli_state *state) +{ + NTSTATUS result = NT_STATUS_LOGON_FAILURE; + fstring name_domain, name_user; + NET_USER_INFO_3 *info3; + + /* Ensure null termination */ + state->request.data.auth.user[sizeof(state->request.data.auth.user)-1]='\0'; + + /* Ensure null termination */ + state->request.data.auth.pass[sizeof(state->request.data.auth.pass)-1]='\0'; + + DEBUG(3, ("[%5lu]: dual pam auth %s\n", (unsigned long)state->pid, + state->request.data.auth.user)); + + /* Parse domain and username */ + + parse_domain_user(state->request.data.auth.user, name_domain, name_user); + + DEBUG(10,("winbindd_dual_pam_auth: domain: %s last was %s\n", domain->name, domain->online ? "online":"offline")); + + /* Check for Kerberos authentication */ + if (domain->online && (state->request.flags & WBFLAG_PAM_KRB5)) { + + result = winbindd_dual_pam_auth_kerberos(domain, state, &info3); + + if (NT_STATUS_IS_OK(result)) { + DEBUG(10,("winbindd_dual_pam_auth_kerberos succeeded\n")); + goto process_result; + } else { + DEBUG(10,("winbindd_dual_pam_auth_kerberos failed: %s\n", nt_errstr(result))); + } + + if (NT_STATUS_EQUAL(result, NT_STATUS_NO_LOGON_SERVERS)) { + DEBUG(10,("winbindd_dual_pam_auth_kerberos setting domain to offline\n")); + domain->online = False; + } + + if (state->request.flags & WBFLAG_PAM_FALLBACK_AFTER_KRB5) { + DEBUG(3,("falling back to samlogon\n")); + goto sam_logon; + } else { + goto cached_logon; + } + } + +sam_logon: + /* Check for Samlogon authentication */ + if (domain->online) { + result = winbindd_dual_pam_auth_samlogon(domain, state, &info3); + + if (NT_STATUS_IS_OK(result)) { + DEBUG(10,("winbindd_dual_pam_auth_samlogon succeeded\n")); + goto process_result; + } else { + DEBUG(10,("winbindd_dual_pam_auth_samlogon failed: %s\n", nt_errstr(result))); + if (domain->online) { + /* We're still online - fail. */ + goto done; + } + /* Else drop through and see if we can check offline.... */ + } + } + +cached_logon: + /* Check for Cached logons */ + if (!domain->online && (state->request.flags & WBFLAG_PAM_CACHED_LOGIN) && + lp_winbind_offline_logon()) { + + result = winbindd_dual_pam_auth_cached(domain, state, &info3); + + if (NT_STATUS_IS_OK(result)) { + DEBUG(10,("winbindd_dual_pam_auth_cached succeeded\n")); + goto process_result; + } else { + DEBUG(10,("winbindd_dual_pam_auth_cached failed: %s\n", nt_errstr(result))); + goto done; + } + } + +process_result: + if (NT_STATUS_IS_OK(result)) { - netsamlogon_cache_store(name_user, &info3); - wcache_invalidate_samlogon(find_domain_from_name(name_domain), &info3); + + netsamlogon_cache_store(name_user, info3); + wcache_invalidate_samlogon(find_domain_from_name(name_domain), info3); /* Check if the user is in the right group */ - if (!NT_STATUS_IS_OK(result = check_info3_in_group(state->mem_ctx, &info3, + if (!NT_STATUS_IS_OK(result = check_info3_in_group(state->mem_ctx, info3, state->request.data.auth.require_membership_of_sid))) { DEBUG(3, ("User %s is not in the required group (%s), so plaintext authentication is rejected\n", state->request.data.auth.user, state->request.data.auth.require_membership_of_sid)); + goto done; } - } -done: + if (state->request.flags & WBFLAG_PAM_INFO3_NDR) { + result = append_info3_as_ndr(state->mem_ctx, state, info3); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10,("Failed to append INFO3 (NDR): %s\n", nt_errstr(result))); + goto done; + } + } + + if (state->request.flags & WBFLAG_PAM_INFO3_TEXT) { + result = append_info3_as_txt(state->mem_ctx, state, info3); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10,("Failed to append INFO3 (TXT): %s\n", nt_errstr(result))); + goto done; + } + + } + if ((state->request.flags & WBFLAG_PAM_CACHED_LOGIN) && + lp_winbind_offline_logon()) { + + result = winbindd_store_creds(domain, + state->mem_ctx, + state->request.data.auth.user, + state->request.data.auth.pass, + info3, NULL); + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10,("Failed to store creds: %s\n", nt_errstr(result))); + goto done; + } + + } + + result = fillup_password_policy(domain, state); + + if (!NT_STATUS_IS_OK(result)) { + DEBUG(10,("Failed to get password policies: %s\n", nt_errstr(result))); + goto done; + } + + } + +done: /* give us a more useful (more correct?) error code */ if ((NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND) || - (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL)))) { + (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL)))) { result = NT_STATUS_NO_LOGON_SERVERS; } @@ -439,8 +1224,8 @@ done: DOM_SID user_sid; fstring sidstr; - sid_copy(&user_sid, &info3.dom_sid.sid); - sid_append_rid(&user_sid, info3.user_rid); + sid_copy(&user_sid, &info3->dom_sid.sid); + sid_append_rid(&user_sid, info3->user_rid); sid_to_string(sidstr, &user_sid); afsname = talloc_string_sub(state->mem_ctx, afsname, "%s", sidstr); @@ -474,10 +1259,11 @@ done: no_token: talloc_free(afsname); } - + return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR; } + /********************************************************************** Challenge Response Authentication Protocol **********************************************************************/ @@ -525,7 +1311,7 @@ void winbindd_pam_auth_crap(struct winbindd_cli_state *state) } if (domain_name != NULL) - domain = find_auth_domain(domain_name); + domain = find_auth_domain(state, domain_name); if (domain != NULL) { sendto_domain(state, domain); @@ -675,6 +1461,7 @@ enum winbindd_result winbindd_dual_pam_auth_crap(struct winbindd_domain *domain, } while ( (attempts < 2) && retry ); if (NT_STATUS_IS_OK(result)) { + netsamlogon_cache_store(name_user, &info3); wcache_invalidate_samlogon(find_domain_from_name(name_domain), &info3); @@ -732,7 +1519,7 @@ done: /* give us a more useful (more correct?) error code */ if ((NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND) || - (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL)))) { + (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL)))) { result = NT_STATUS_NO_LOGON_SERVERS; } @@ -763,12 +1550,16 @@ done: void winbindd_pam_chauthtok(struct winbindd_cli_state *state) { - NTSTATUS result; - char *oldpass, *newpass; + NTSTATUS result = NT_STATUS_UNSUCCESSFUL; + char *oldpass; + char *newpass = NULL; fstring domain, user; POLICY_HND dom_pol; struct winbindd_domain *contact_domain; struct rpc_pipe_client *cli; + BOOL got_info = False; + SAM_UNK_INFO_1 *info; + SAMR_CHANGE_REJECT *reject; DEBUG(3, ("[%5lu]: pam chauthtok %s\n", (unsigned long)state->pid, state->request.data.chauthtok.user)); @@ -798,10 +1589,70 @@ void winbindd_pam_chauthtok(struct winbindd_cli_state *state) goto done; } - result = rpccli_samr_chgpasswd_user(cli, state->mem_ctx, user, newpass, - oldpass); + result = rpccli_samr_chgpasswd3(cli, state->mem_ctx, user, newpass, oldpass, &info, &reject); + + /* FIXME: need to check for other error codes ? */ + if (NT_STATUS_EQUAL(result, NT_STATUS_PASSWORD_RESTRICTION)) { + + state->response.data.auth.policy.min_length_password = + info->min_length_password; + state->response.data.auth.policy.password_history = + info->password_history; + state->response.data.auth.policy.password_properties = + info->password_properties; + state->response.data.auth.policy.expire = + nt_time_to_unix_abs(&info->expire); + state->response.data.auth.policy.min_passwordage = + nt_time_to_unix_abs(&info->min_passwordage); + + state->response.data.auth.reject_reason = + reject->reject_reason; + + got_info = True; + + } else if (!NT_STATUS_IS_OK(result)) { + + DEBUG(10,("Password change with chgpasswd3 failed with: %s, retrying chgpasswd_user\n", + nt_errstr(result))); + + state->response.data.auth.reject_reason = 0; + + result = rpccli_samr_chgpasswd_user(cli, state->mem_ctx, user, newpass, oldpass); + } + +done: + if (NT_STATUS_IS_OK(result) && (state->request.flags & WBFLAG_PAM_CACHED_LOGIN) && + lp_winbind_offline_logon()) { + + NTSTATUS cred_ret; + + cred_ret = winbindd_update_creds_by_name(contact_domain, + state->mem_ctx, user, + newpass); + if (!NT_STATUS_IS_OK(cred_ret)) { + DEBUG(10,("Failed to store creds: %s\n", nt_errstr(cred_ret))); + goto process_result; /* FIXME: hm, risking inconsistant cache ? */ + } + } + + if (!NT_STATUS_IS_OK(result) && !got_info) { + + NTSTATUS policy_ret; + + policy_ret = fillup_password_policy(contact_domain, state); + + /* failure of this is non critical, it will just provide no + * additional information to the client why the change has + * failed - Guenther */ + + if (!NT_STATUS_IS_OK(policy_ret)) { + DEBUG(10,("Failed to get password policies: %s\n", nt_errstr(policy_ret))); + goto process_result; + } + } + +process_result: -done: state->response.data.auth.nt_status = NT_STATUS_V(result); fstrcpy(state->response.data.auth.nt_status_string, nt_errstr(result)); fstrcpy(state->response.data.auth.error_string, get_friendly_nt_error_msg(result)); @@ -819,3 +1670,112 @@ done: else request_error(state); } + +void winbindd_pam_logoff(struct winbindd_cli_state *state) +{ + struct winbindd_domain *domain; + fstring name_domain, user; + + DEBUG(3, ("[%5lu]: pam logoff %s\n", (unsigned long)state->pid, + state->request.data.logoff.user)); + + /* Ensure null termination */ + state->request.data.logoff.user + [sizeof(state->request.data.logoff.user)-1]='\0'; + + state->request.data.logoff.krb5ccname + [sizeof(state->request.data.logoff.krb5ccname)-1]='\0'; + + parse_domain_user(state->request.data.logoff.user, name_domain, user); + + domain = find_auth_domain(state, name_domain); + + if (domain == NULL) { + set_auth_errors(&state->response, NT_STATUS_NO_SUCH_USER); + DEBUG(5, ("Pam Logoff for %s returned %s " + "(PAM: %d)\n", + state->request.data.auth.user, + state->response.data.auth.nt_status_string, + state->response.data.auth.pam_error)); + request_error(state); + return; + } + + sendto_domain(state, domain); +} + +enum winbindd_result winbindd_dual_pam_logoff(struct winbindd_domain *domain, + struct winbindd_cli_state *state) +{ + NTSTATUS result = NT_STATUS_NOT_SUPPORTED; + struct WINBINDD_CCACHE_ENTRY *entry; + int ret; + + DEBUG(3, ("[%5lu]: pam dual logoff %s\n", (unsigned long)state->pid, + state->request.data.logoff.user)); + + if (!(state->request.flags & WBFLAG_PAM_KRB5)) { + result = NT_STATUS_OK; + goto process_result; + } + +#ifdef HAVE_KRB5 + + /* what we need here is to find the corresponding krb5 ccache name *we* + * created for a given username and destroy it (as the user who created it) */ + + entry = get_ccache_by_username(state->request.data.logoff.user); + if (entry == NULL) { + DEBUG(10,("winbindd_pam_logoff: could not get ccname for user %s\n", + state->request.data.logoff.user)); + goto process_result; + } + + DEBUG(10,("winbindd_pam_logoff: found ccache [%s]\n", entry->ccname)); + + if (entry->uid < 0 || state->request.data.logoff.uid < 0) { + DEBUG(0,("winbindd_pam_logoff: invalid uid\n")); + goto process_result; + } + + if (entry->uid != state->request.data.logoff.uid) { + DEBUG(0,("winbindd_pam_logoff: uid's differ: %d != %d\n", + entry->uid, state->request.data.logoff.uid)); + goto process_result; + } + + if (!strcsequal(entry->ccname, state->request.data.logoff.krb5ccname)) { + DEBUG(0,("winbindd_pam_logoff: krb5ccnames differ: (daemon) %s != (client) %s\n", + entry->ccname, state->request.data.logoff.krb5ccname)); + goto process_result; + } + + seteuid(entry->uid); + + ret = ads_kdestroy(entry->ccname); + + seteuid(0); + + if (ret) { + DEBUG(0,("winbindd_pam_logoff: failed to destroy user ccache %s with: %s\n", + entry->ccname, error_message(ret))); + } else { + DEBUG(10,("winbindd_pam_logoff: successfully destroyed ccache %s for user %s\n", + entry->ccname, state->request.data.logoff.user)); + remove_ccache_by_ccname(entry->ccname); + } + + result = krb5_to_nt_status(ret); +#else + result = NT_STATUS_NOT_SUPPORTED; +#endif + +process_result: + state->response.data.auth.nt_status = NT_STATUS_V(result); + fstrcpy(state->response.data.auth.nt_status_string, nt_errstr(result)); + fstrcpy(state->response.data.auth.error_string, get_friendly_nt_error_msg(result)); + state->response.data.auth.pam_error = nt_status_to_pam(result); + + return NT_STATUS_IS_OK(result) ? WINBINDD_OK : WINBINDD_ERROR; +} + diff --git a/source3/nsswitch/winbindd_passdb.c b/source3/nsswitch/winbindd_passdb.c index c32aa01a38..96a85a4f3a 100644 --- a/source3/nsswitch/winbindd_passdb.c +++ b/source3/nsswitch/winbindd_passdb.c @@ -151,7 +151,8 @@ BOOL fill_passdb_alias_grmem(struct winbindd_domain *domain, *gr_mem = NULL; *gr_mem_len = 0; - if (!pdb_enum_aliasmem(group_sid, &members, &num_members)) + if (!NT_STATUS_IS_OK(pdb_enum_aliasmem(group_sid, &members, + &num_members))) return True; for (i=0; iname); - *name = talloc_strdup(mem_ctx, info.acct_name); - if (sid_check_is_in_builtin(sid)) - *type = SID_NAME_WKN_GRP; - else - *type = SID_NAME_ALIAS; + if (!lookup_sid(mem_ctx, sid, &dom, &nam, type)) { + return NT_STATUS_NONE_MAPPED; + } + + *domain_name = talloc_strdup(mem_ctx, dom); + *name = talloc_strdup(mem_ctx, nam); return NT_STATUS_OK; } @@ -305,14 +311,14 @@ static NTSTATUS lookup_useraliases(struct winbindd_domain *domain, uint32 num_sids, const DOM_SID *sids, uint32 *p_num_aliases, uint32 **rids) { - BOOL result; + NTSTATUS result; size_t num_aliases = 0; result = pdb_enum_alias_memberships(mem_ctx, &domain->sid, sids, num_sids, rids, &num_aliases); *p_num_aliases = num_aliases; - return result ? NT_STATUS_OK : NT_STATUS_UNSUCCESSFUL; + return result; } /* Lookup group membership given a rid. */ @@ -322,16 +328,106 @@ static NTSTATUS lookup_groupmem(struct winbindd_domain *domain, DOM_SID **sid_mem, char ***names, uint32 **name_types) { + size_t i, num_members, num_mapped; + uint32 *rids; + NTSTATUS result; + const DOM_SID **sids; + struct lsa_dom_info *lsa_domains; + struct lsa_name_info *lsa_names; + + if (!sid_check_is_in_our_domain(group_sid)) { + /* There's no groups, only aliases in BUILTIN */ + return NT_STATUS_NO_SUCH_GROUP; + } + + result = pdb_enum_group_members(mem_ctx, group_sid, &rids, + &num_members); + if (!NT_STATUS_IS_OK(result)) { + return result; + } + + if (num_members == 0) { + *num_names = 0; + *sid_mem = NULL; + *names = NULL; + *name_types = NULL; + return NT_STATUS_OK; + } + + *sid_mem = TALLOC_ARRAY(mem_ctx, DOM_SID, num_members); + *names = TALLOC_ARRAY(mem_ctx, char *, num_members); + *name_types = TALLOC_ARRAY(mem_ctx, uint32, num_members); + sids = TALLOC_ARRAY(mem_ctx, const DOM_SID *, num_members); + + if (((*sid_mem) == NULL) || ((*names) == NULL) || + ((*name_types) == NULL) || (sids == NULL)) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0; isid); + sid_append_rid(sid, rids[i]); + sids[i] = sid; + } + + result = lookup_sids(mem_ctx, num_members, sids, 1, + &lsa_domains, &lsa_names); + if (!NT_STATUS_IS_OK(result)) { + return result; + } + + num_mapped = 0; + for (i=0; iname) == -1) { - return NT_STATUS_NO_MEMORY; - } - (*alt_names)[*num_domains] = NULL; - (*dom_sids)[*num_domains] = domains[i]->sid; - (*num_domains)++; - } - } while (NT_STATUS_EQUAL(nt_status, STATUS_MORE_ENTRIES)); + nt_status = secrets_trusted_domains(mem_ctx, num_domains, + &domains); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } - if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_MORE_ENTRIES)) { - return NT_STATUS_OK; + *names = TALLOC_ARRAY(mem_ctx, char *, *num_domains); + *alt_names = TALLOC_ARRAY(mem_ctx, char *, *num_domains); + *dom_sids = TALLOC_ARRAY(mem_ctx, DOM_SID, *num_domains); + + if ((*alt_names == NULL) || (*names == NULL) || (*dom_sids == NULL)) { + return NT_STATUS_NO_MEMORY; } - return nt_status; + + for (i=0; i<*num_domains; i++) { + (*alt_names)[i] = NULL; + (*names)[i] = talloc_steal((*names), domains[i]->name); + sid_copy(&(*dom_sids)[i], &domains[i]->sid); + } + + return NT_STATUS_OK; } /* the rpc backend methods are exposed via this structure */ @@ -391,5 +481,7 @@ struct winbindd_methods passdb_methods = { lookup_useraliases, lookup_groupmem, sequence_number, + lockout_policy, + password_policy, trusted_domains, }; diff --git a/source3/nsswitch/winbindd_reconnect.c b/source3/nsswitch/winbindd_reconnect.c index 77df9c1513..e37bfcad97 100644 --- a/source3/nsswitch/winbindd_reconnect.c +++ b/source3/nsswitch/winbindd_reconnect.c @@ -220,6 +220,36 @@ static NTSTATUS sequence_number(struct winbindd_domain *domain, uint32 *seq) return result; } +/* find the lockout policy of a domain */ +static NTSTATUS lockout_policy(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + SAM_UNK_INFO_12 *lockout_policy) +{ + NTSTATUS result; + + result = msrpc_methods.lockout_policy(domain, mem_ctx, lockout_policy); + + if (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL)) + result = msrpc_methods.lockout_policy(domain, mem_ctx, lockout_policy); + + return result; +} + +/* find the password policy of a domain */ +static NTSTATUS password_policy(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + SAM_UNK_INFO_1 *password_policy) +{ + NTSTATUS result; + + result = msrpc_methods.password_policy(domain, mem_ctx, password_policy); + + if (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL)) + result = msrpc_methods.password_policy(domain, mem_ctx, password_policy); + + return result; +} + /* get a list of trusted domains */ static NTSTATUS trusted_domains(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx, @@ -255,5 +285,7 @@ struct winbindd_methods reconnect_methods = { lookup_useraliases, lookup_groupmem, sequence_number, + lockout_policy, + password_policy, trusted_domains, }; diff --git a/source3/nsswitch/winbindd_rpc.c b/source3/nsswitch/winbindd_rpc.c index 6179189e30..4aaedad4a2 100644 --- a/source3/nsswitch/winbindd_rpc.c +++ b/source3/nsswitch/winbindd_rpc.c @@ -269,7 +269,7 @@ NTSTATUS msrpc_name_to_sid(struct winbindd_domain *domain, return result; result = rpccli_lsa_lookup_names(cli, mem_ctx, &lsa_policy, 1, - &full_name, &sids, &types); + &full_name, NULL, &sids, &types); if (!NT_STATUS_IS_OK(result)) return result; @@ -883,6 +883,71 @@ static NTSTATUS trusted_domains(struct winbindd_domain *domain, return result; } +/* find the lockout policy for a domain */ +NTSTATUS msrpc_lockout_policy(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + SAM_UNK_INFO_12 *lockout_policy) +{ + NTSTATUS result; + struct rpc_pipe_client *cli; + POLICY_HND dom_pol; + SAM_UNK_CTR ctr; + + DEBUG(10,("rpc: fetch lockout policy for %s\n", domain->name)); + + result = cm_connect_sam(domain, mem_ctx, &cli, &dom_pol); + if (!NT_STATUS_IS_OK(result)) { + goto done; + } + + result = rpccli_samr_query_dom_info(cli, mem_ctx, &dom_pol, 12, &ctr); + if (!NT_STATUS_IS_OK(result)) { + goto done; + } + + *lockout_policy = ctr.info.inf12; + + DEBUG(10,("msrpc_lockout_policy: bad_attempt_lockout %d\n", + ctr.info.inf12.bad_attempt_lockout)); + + done: + + return result; +} + +/* find the password policy for a domain */ +NTSTATUS msrpc_password_policy(struct winbindd_domain *domain, + TALLOC_CTX *mem_ctx, + SAM_UNK_INFO_1 *password_policy) +{ + NTSTATUS result; + struct rpc_pipe_client *cli; + POLICY_HND dom_pol; + SAM_UNK_CTR ctr; + + DEBUG(10,("rpc: fetch password policy for %s\n", domain->name)); + + result = cm_connect_sam(domain, mem_ctx, &cli, &dom_pol); + if (!NT_STATUS_IS_OK(result)) { + goto done; + } + + result = rpccli_samr_query_dom_info(cli, mem_ctx, &dom_pol, 1, &ctr); + if (!NT_STATUS_IS_OK(result)) { + goto done; + } + + *password_policy = ctr.info.inf1; + + DEBUG(10,("msrpc_password_policy: min_length_password %d\n", + ctr.info.inf1.min_length_password)); + + done: + + return result; +} + + /* the rpc backend methods are exposed via this structure */ struct winbindd_methods msrpc_methods = { False, @@ -896,5 +961,7 @@ struct winbindd_methods msrpc_methods = { msrpc_lookup_useraliases, lookup_groupmem, sequence_number, + msrpc_lockout_policy, + msrpc_password_policy, trusted_domains, }; diff --git a/source3/nsswitch/winbindd_sid.c b/source3/nsswitch/winbindd_sid.c index fc878cb5cc..a4cd8f7604 100644 --- a/source3/nsswitch/winbindd_sid.c +++ b/source3/nsswitch/winbindd_sid.c @@ -506,10 +506,10 @@ static void gid2sid_idmap_set_mapping_recv(void *private_data, BOOL success) request_ok(state->cli_state); } -void winbindd_allocate_rid(struct winbindd_cli_state *state) +void winbindd_allocate_uid(struct winbindd_cli_state *state) { if ( !state->privileged ) { - DEBUG(2, ("winbindd_allocate_rid: non-privileged access " + DEBUG(2, ("winbindd_allocate_uid: non-privileged access " "denied!\n")); request_error(state); return; @@ -518,25 +518,22 @@ void winbindd_allocate_rid(struct winbindd_cli_state *state) sendto_child(state, idmap_child()); } -enum winbindd_result winbindd_dual_allocate_rid(struct winbindd_domain *domain, +enum winbindd_result winbindd_dual_allocate_uid(struct winbindd_domain *domain, struct winbindd_cli_state *state) { - /* We tell idmap to always allocate a user RID. There might be a good - * reason to keep RID allocation for users to even and groups to - * odd. This needs discussion I think. For now only allocate user - * rids. */ + union unid_t id; - if (!NT_STATUS_IS_OK(idmap_allocate_rid(&state->response.data.rid, - USER_RID_TYPE))) + if (!NT_STATUS_IS_OK(idmap_allocate_id(&id, ID_USERID))) { return WINBINDD_ERROR; - + } + state->response.data.uid = id.uid; return WINBINDD_OK; } -void winbindd_allocate_rid_and_gid(struct winbindd_cli_state *state) +void winbindd_allocate_gid(struct winbindd_cli_state *state) { if ( !state->privileged ) { - DEBUG(2, ("winbindd_allocate_rid: non-privileged access " + DEBUG(2, ("winbindd_allocate_gid: non-privileged access " "denied!\n")); request_error(state); return; @@ -545,30 +542,15 @@ void winbindd_allocate_rid_and_gid(struct winbindd_cli_state *state) sendto_child(state, idmap_child()); } -enum winbindd_result winbindd_dual_allocate_rid_and_gid(struct winbindd_domain *domain, - struct winbindd_cli_state *state) +enum winbindd_result winbindd_dual_allocate_gid(struct winbindd_domain *domain, + struct winbindd_cli_state *state) { - NTSTATUS result; - DOM_SID sid; - - /* We tell idmap to always allocate a user RID. This is really - * historic and needs to be fixed. I *think* this has to do with the - * way winbind determines its free RID space. */ - - result = idmap_allocate_rid(&state->response.data.rid_and_gid.rid, - USER_RID_TYPE); + union unid_t id; - if (!NT_STATUS_IS_OK(result)) + if (!NT_STATUS_IS_OK(idmap_allocate_id(&id, ID_GROUPID))) { return WINBINDD_ERROR; - - sid_copy(&sid, get_global_sam_sid()); - sid_append_rid(&sid, state->response.data.rid_and_gid.rid); - - result = idmap_sid_to_gid(&sid, &state->response.data.rid_and_gid.gid, - 0); - - if (!NT_STATUS_IS_OK(result)) - return WINBINDD_ERROR; - + } + state->response.data.gid = id.gid; return WINBINDD_OK; } + diff --git a/source3/nsswitch/winbindd_user.c b/source3/nsswitch/winbindd_user.c index 0b88d5eee5..9670bf534c 100644 --- a/source3/nsswitch/winbindd_user.c +++ b/source3/nsswitch/winbindd_user.c @@ -122,10 +122,10 @@ static BOOL winbindd_fill_pwent(char *dom_name, char *user_name, pw->pw_uid, pw->pw_gid, shell, pw->pw_shell)) return False; - /* Password - set to "x" as we can't generate anything useful here. + /* Password - set to "*" as we can't generate anything useful here. Authentication can be done using the pam_winbind module. */ - safe_strcpy(pw->pw_passwd, "x", sizeof(pw->pw_passwd) - 1); + safe_strcpy(pw->pw_passwd, "*", sizeof(pw->pw_passwd) - 1); return True; } @@ -307,10 +307,10 @@ static void getpwsid_sid2gid_recv(void *private_data, BOOL success, gid_t gid) goto failed; } - /* Password - set to "x" as we can't generate anything useful here. + /* Password - set to "*" as we can't generate anything useful here. Authentication can be done using the pam_winbind module. */ - safe_strcpy(pw->pw_passwd, "x", sizeof(pw->pw_passwd) - 1); + safe_strcpy(pw->pw_passwd, "*", sizeof(pw->pw_passwd) - 1); request_ok(s->state); return; diff --git a/source3/nsswitch/winbindd_util.c b/source3/nsswitch/winbindd_util.c index 4c3306a8ac..b92ee0de82 100644 --- a/source3/nsswitch/winbindd_util.c +++ b/source3/nsswitch/winbindd_util.c @@ -161,6 +161,7 @@ static struct winbindd_domain *add_trusted_domain(const char *domain_name, const domain->sequence_number = DOM_SEQUENCE_NONE; domain->last_seq_check = 0; domain->initialized = False; + domain->online = False; if (sid) { sid_copy(&domain->sid, sid); } @@ -334,6 +335,7 @@ enum winbindd_result init_child_connection(struct winbindd_domain *domain, struct winbindd_request *request; struct winbindd_response *response; struct init_child_state *state; + struct winbindd_domain *request_domain; mem_ctx = talloc_init("init_child_connection"); if (mem_ctx == NULL) { @@ -366,7 +368,6 @@ enum winbindd_result init_child_connection(struct winbindd_domain *domain, fstrcpy(request->domain_name, domain->name); request->data.init_conn.is_primary = True; fstrcpy(request->data.init_conn.dcname, ""); - async_request(mem_ctx, &domain->child, request, response, init_child_recv, state); return WINBINDD_PENDING; @@ -378,7 +379,11 @@ enum winbindd_result init_child_connection(struct winbindd_domain *domain, request->cmd = WINBINDD_GETDCNAME; fstrcpy(request->domain_name, domain->name); - async_domain_request(mem_ctx, find_our_domain(), request, response, + /* save online flag */ + request_domain = find_our_domain(); + request_domain->online = domain->online; + + async_domain_request(mem_ctx, request_domain, request, response, init_child_getdc_recv, state); return WINBINDD_PENDING; } @@ -1079,10 +1084,6 @@ static int convert_fn(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA data, void *state #define HWM_GROUP "GROUP HWM" #define HWM_USER "USER HWM" -/* idmap version determines auto-conversion */ -#define IDMAP_VERSION 2 - - /***************************************************************************** Convert the idmap database from an older version. *****************************************************************************/ -- cgit