parent.
****************************************************************/
+bool winbindd_reinit_after_fork(const char *logfile);
+
static BOOL fork_child_dc_connect(struct winbindd_domain *domain)
{
struct dc_name_ip *dcs = NULL;
TALLOC_CTX *mem_ctx = NULL;
pid_t child_pid;
pid_t parent_pid = sys_getpid();
+ pstring logfile;
/* Stop zombies */
CatchChild();
/* Leave messages blocked - we will never process one. */
/* tdb needs special fork handling */
- if (tdb_reopen_all(1) == -1) {
- DEBUG(0,("tdb_reopen_all failed.\n"));
- _exit(0);
- }
-
- close_conns_after_fork();
-
if (!override_logfile) {
- pstring logfile;
pstr_sprintf(logfile, "%s/log.winbindd-dc-connect", dyn_LOGFILEBASE);
- lp_set_logfile(logfile);
- reopen_logs();
+ }
+ if (!winbindd_reinit_after_fork(logfile)) {
+ DEBUG(0,("winbindd_reinit_after_fork failed.\n"));
+ message_send_pid(pid_to_procid(parent_pid), MSG_WINBIND_FAILED_TO_GO_ONLINE,
+ domain->name,
+ strlen(domain->name)+1, False);
+ _exit(0);
}
mem_ctx = talloc_init("fork_child_dc_connect");
Set domain online - if allowed.
****************************************************************/
+void ccache_regain_all_now(void);
+
static void set_domain_online(struct winbindd_domain *domain)
{
- struct timeval now;
-
DEBUG(10,("set_domain_online: called for domain %s\n",
domain->name ));
}
/* If we are waiting to get a krb5 ticket, trigger immediately. */
- GetTimeOfDay(&now);
- set_event_dispatch_time(winbind_event_context(),
- "krb5_ticket_gain_handler", now);
+ ccache_regain_all_now();
/* Ok, we're out of any startup mode now... */
domain->startup = False;
try and connect to a DC. But I don't believe it
because network manager seems to lie.
Wait at least 5 seconds. Heuristics suck... */
+ GetTimeOfDay(&tev);
+
+ /* Go into "startup" mode again. */
+ domain->startup_time = tev.tv_sec;
+ domain->startup = True;
+
+ tev.tv_sec += 5;
if (!domain->check_online_event) {
/* If we've come from being globally offline we
DEBUG(10,("set_domain_online_request: domain %s was globally offline.\n",
domain->name ));
- domain->check_online_event = event_add_timed(winbind_event_context(),
- NULL,
- timeval_current_ofs(5, 0),
- "check_domain_online_handler",
- check_domain_online_handler,
- domain);
-
- /* The above *has* to succeed for winbindd to work. */
- if (!domain->check_online_event) {
- smb_panic("set_domain_online_request: failed to add online handler.\n");
- }
}
+ TALLOC_FREE(domain->check_online_event);
+
+ domain->check_online_event = event_add_timed(winbind_event_context(),
+ NULL,
+ tev,
+ "check_domain_online_handler",
+ check_domain_online_handler,
+ domain);
- GetTimeOfDay(&tev);
-
- /* Go into "startup" mode again. */
- domain->startup_time = tev.tv_sec;
- domain->startup = True;
-
- tev.tv_sec += 5;
-
- set_event_dispatch_time(winbind_event_context(), "check_domain_online_handler", tev);
+ /* The above *has* to succeed for winbindd to work. */
+ if (!domain->check_online_event) {
+ smb_panic("set_domain_online_request: failed to add online handler.\n");
+ }
}
/****************************************************************
#define MAX_CCACHES 100
static struct WINBINDD_CCACHE_ENTRY *ccache_list;
+static void add_krb5_ticket_gain_handler_event(struct WINBINDD_CCACHE_ENTRY *entry,
+ struct timeval t);
/* The Krb5 ticket refresh handler should be scheduled
at one-half of the period from now till the tkt
return i;
}
+void ccache_remove_all_after_fork(void)
+{
+ struct WINBINDD_CCACHE_ENTRY *cur;
+ cur = ccache_list;
+ while (cur) {
+ DLIST_REMOVE(ccache_list, cur);
+ TALLOC_FREE(cur->event);
+ TALLOC_FREE(cur);
+ cur = ccache_list;
+ }
+}
+
+static void krb5_ticket_gain_handler(struct event_context *event_ctx,
+ struct timed_event *te,
+ const struct timeval *now,
+ void *private_data);
+static void krb5_ticket_refresh_handler(struct event_context *event_ctx,
+ struct timed_event *te,
+ const struct timeval *now,
+ void *private_data);
+
+void ccache_regain_all_now(void)
+{
+ struct WINBINDD_CCACHE_ENTRY *cur;
+ struct timeval t = timeval_current();
+
+ cur = ccache_list;
+ while (cur) {
+ TALLOC_FREE(cur->event);
+ if (cur->refresh_time) {
+ cur->event = event_add_timed(winbind_event_context(),
+ cur, t,
+ "krb5_ticket_refresh_handler",
+ krb5_ticket_refresh_handler,
+ cur);
+ } else {
+ cur->event = event_add_timed(winbind_event_context(),
+ cur, t,
+ "krb5_ticket_gain_handler",
+ krb5_ticket_gain_handler,
+ cur);
+ }
+ if (!cur->event) {
+ DEBUG(0, ("ccache_regain_all_now: out of memory!!\n"));
+ }
+ }
+ return;
+}
+
+/****************************************************************
+ The gain initial ticket is recognized as entry->refresh_time is
+ always zero.
+****************************************************************/
+
+static void add_krb5_ticket_gain_handler_event(struct WINBINDD_CCACHE_ENTRY *entry,
+ struct timeval t)
+{
+ entry->refresh_time = 0;
+ entry->event = event_add_timed(winbind_event_context(), entry,
+ t,
+ "krb5_ticket_gain_handler",
+ krb5_ticket_gain_handler,
+ entry);
+}
+
/****************************************************************
Do the work of refreshing the ticket.
****************************************************************/
#ifdef HAVE_KRB5
int ret;
time_t new_start;
+ time_t expire_time = 0;
struct WINBINDD_MEMORY_CREDS *cred_ptr = entry->cred_ptr;
#endif
#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)) && cred_ptr && cred_ptr->pass) {
-
- set_effective_uid(entry->uid);
-
- ret = kerberos_kinit_password_ext(entry->principal_name,
- cred_ptr->pass,
- 0, /* hm, can we do time correction here ? */
- &entry->refresh_time,
- &entry->renew_until,
- entry->ccname,
- False, /* no PAC required anymore */
- True,
- WINBINDD_PAM_AUTH_KRB5_RENEW_TIME);
- gain_root_privilege();
-
- if (ret) {
- DEBUG(3,("krb5_ticket_refresh_handler: could not re-kinit: %s\n",
- error_message(ret)));
- TALLOC_FREE(entry->event);
- return;
- }
-
- DEBUG(10,("krb5_ticket_refresh_handler: successful re-kinit "
- "for: %s in ccache: %s\n",
- entry->principal_name, entry->ccname));
-
+ * tgt anymore
+ * NB
+ * This happens when machine are put to sleep for a very long time. */
+
+ if (entry->renew_until < time(NULL)) {
+rekinit:
+ if (cred_ptr && cred_ptr->pass) {
+
+ set_effective_uid(entry->uid);
+
+ ret = kerberos_kinit_password_ext(entry->principal_name,
+ cred_ptr->pass,
+ 0, /* hm, can we do time correction here ? */
+ &entry->refresh_time,
+ &entry->renew_until,
+ entry->ccname,
+ False, /* no PAC required anymore */
+ True,
+ WINBINDD_PAM_AUTH_KRB5_RENEW_TIME);
+ gain_root_privilege();
+
+ if (ret) {
+ DEBUG(3,("krb5_ticket_refresh_handler: "
+ "could not re-kinit: %s\n",
+ error_message(ret)));
+ /* destroy the ticket because we cannot rekinit
+ * it, ignore error here */
+ ads_kdestroy(entry->ccname);
+
+ /* Don't break the ticket refresh chain: retry
+ * refreshing ticket sometime later when KDC is
+ * unreachable -- BoYang.
+ * More error handling here? KRB5_CC_IO,
+ * KRB5KRB_AP_ERR_SKEW.
+ * */
+
+ if ((ret == KRB5_KDC_UNREACH)
+ || (ret == KRB5_REALM_CANT_RESOLVE)) {
#if defined(DEBUG_KRB5_TKT_RENEWAL)
- new_start = time(NULL) + 30;
+ new_start = time(NULL) + 30;
#else
- /* The tkt should be refreshed at one-half the period
- from now to the expiration time */
- new_start = KRB5_EVENT_REFRESH_TIME(entry->refresh_time);
+ new_start = time(NULL) +
+ MAX(30, lp_winbind_cache_time());
#endif
-
- goto done;
- }
+ /* try to regain ticket here */
+ add_krb5_ticket_gain_handler_event(entry,
+ timeval_set(new_start, 0));
+ return;
+ }
+ TALLOC_FREE(entry->event);
+ return;
+ }
+
+ DEBUG(10,("krb5_ticket_refresh_handler: successful re-kinit "
+ "for: %s in ccache: %s\n",
+ entry->principal_name, entry->ccname));
+
+#if defined(DEBUG_KRB5_TKT_RENEWAL)
+ new_start = time(NULL) + 30;
+#else
+ /* The tkt should be refreshed at one-half the period
+ from now to the expiration time */
+ expire_time = entry->refresh_time;
+ new_start = KRB5_EVENT_REFRESH_TIME(entry->refresh_time);
+#endif
+ goto done;
+ } else {
+ /* can this happen?
+ * No cached credentials
+ * destroy ticket and refresh chain
+ * */
+ ads_kdestroy(entry->ccname);
+ TALLOC_FREE(entry->event);
+ return;
+ }
+ }
set_effective_uid(entry->uid);
#if defined(DEBUG_KRB5_TKT_RENEWAL)
new_start = time(NULL) + 30;
#else
+ expire_time = new_start;
new_start = KRB5_EVENT_REFRESH_TIME(new_start);
#endif
error_message(ret)));
/* maybe we are beyond the renewing window */
+ /* evil rises here, we refresh ticket failed,
+ * but the ticket might be expired. Therefore,
+ * When we refresh ticket failed, destory the
+ * ticket */
+
+ ads_kdestroy(entry->ccname);
+
/* avoid breaking the renewal chain: retry in lp_winbind_cache_time()
- * seconds when the KDC was not available right now. */
-
- if (ret == KRB5_KDC_UNREACH) {
- new_start = time(NULL) + MAX(30, lp_winbind_cache_time());
- goto done;
- }
+ * seconds when the KDC was not available right now.
+ * the return code can be KRB5_REALM_CANT_RESOLVE
+ * More error handling here? KRB5_CC_IO, KRB5KRB_AP_ERR_SKEW. */
+
+ if ((ret == KRB5_KDC_UNREACH)
+ || (ret == KRB5_REALM_CANT_RESOLVE)) {
+#if defined(DEBUG_KRB5_TKT_RENEWAL)
+ new_start = time(NULL) + 30;
+#else
+ new_start = time(NULL) +
+ MAX(30, lp_winbind_cache_time());
+#endif
+ /* ticket is destroyed here, we have to regain it
+ * if it is possible */
+ add_krb5_ticket_gain_handler_event(entry, timeval_set(new_start, 0));
+ return;
+ }
+ /* This is evil, if the ticket was already expired.
+ * renew ticket function returns KRB5KRB_AP_ERR_TKT_EXPIRED.
+ * But there is still a chance that we can rekinit it.
+ *
+ * This happens when user login in online mode, and then network
+ * down or something cause winbind goes offline for a very long time,
+ * and then goes online again. ticket expired, renew failed.
+ * This happens when machine are put to sleep for a long time,
+ * but shorter than entry->renew_util.
+ * NB
+ * Looks like the KDC is reachable, we want to rekinit as soon as
+ * possible instead of waiting some time later. */
+ if ((ret == KRB5KRB_AP_ERR_TKT_EXPIRED)
+ || (ret == KRB5_FCC_NOFILE)) goto rekinit;
+
return;
}
done:
-
+ /* in cases that ticket will be unrenewable soon, we don't try to renew ticket
+ * but try to regain ticket if it is possible */
+ if (entry->renew_until && expire_time
+ && (entry->renew_until <= expire_time)) {
+ /* try to regain ticket 10 seconds beforre expiration */
+ expire_time -= 10;
+ add_krb5_ticket_gain_handler_event(entry, timeval_set(expire_time, 0));
+ return;
+ }
+
+ if (!entry->refresh_time) {
+ entry->refresh_time = new_start;
+ }
entry->event = event_add_timed(winbind_event_context(), entry,
timeval_set(new_start, 0),
"krb5_ticket_refresh_handler",
if (ret) {
DEBUG(3,("krb5_ticket_gain_handler: could not kinit: %s\n",
error_message(ret)));
+ /* evil. If we cannot do it, destroy any the __maybe__
+ * __existing__ ticket */
+ ads_kdestroy(entry->ccname);
goto retry_later;
}
}
retry_later:
-
- entry->event = event_add_timed(winbind_event_context(), entry,
- timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0),
- "krb5_ticket_gain_handler",
- krb5_ticket_gain_handler,
- entry);
+#if defined(DEBUG_KRB5_TKT_REGAIN)
+ t = timeval_set(time(NULL) + 30, 0);
+#else
+ t = timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0);
+#endif
+ add_krb5_ticket_gain_handler_event(entry, t);
return;
#else
t = timeval_set(KRB5_EVENT_REFRESH_TIME(entry->refresh_time), 0);
#endif
-
+
+ if (!entry->refresh_time) {
+ entry->refresh_time = t.tv_sec;
+ }
entry->event = event_add_timed(winbind_event_context(), entry,
t,
"krb5_ticket_refresh_handler",
BOOL postponed_request)
{
struct WINBINDD_CCACHE_ENTRY *entry = NULL;
+ NTSTATUS ntret;
+#ifdef HAVE_KRB5
+ int ret;
+#endif
if ((username == NULL && princ_name == NULL) || ccname == NULL || uid < 0) {
return NT_STATUS_INVALID_PARAMETER;
return NT_STATUS_NO_MORE_ENTRIES;
}
+ /* If it is cached login, destroy krb5 ticket
+ * to avoid surprise. */
+#ifdef HAVE_KRB5
+ if (postponed_request) {
+ /* ignore KRB5_FCC_NOFILE error here */
+ ret = ads_kdestroy(ccname);
+ if (ret == KRB5_FCC_NOFILE) {
+ ret = 0;
+ }
+ if (ret) {
+ DEBUG(0, ("add_ccache_to_list: failed to destroy "
+ "user krb5 ccache %s with %s\n", ccname,
+ error_message(ret)));
+ return krb5_to_nt_status(ret);
+ } else {
+ DEBUG(10, ("add_ccache_to_list: successfully destroyed "
+ "krb5 ccache %s for user %s\n", ccname,
+ username));
+ }
+ }
+#endif
+
/* Reference count old entries */
entry = get_ccache_by_username(username);
if (entry) {
DEBUG(10,("add_ccache_to_list: ref count on entry %s is now %d\n",
username, entry->ref_count));
/* FIXME: in this case we still might want to have a krb5 cred
- * event handler created - gd*/
+ * event handler created - gd
+ * Add ticket refresh handler here */
+
+ if (!lp_winbind_refresh_tickets() || renew_until <= 0) {
+ return NT_STATUS_OK;
+ }
+
+ if (!entry->event) {
+ struct timeval t;
+ if (postponed_request) {
+ t = timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0);
+ add_krb5_ticket_gain_handler_event(entry, t);
+ } else {
+ /* Renew at 1/2 the ticket expiration time */
+#if defined(DEBUG_KRB5_TKT_RENEWAL)
+ t = timeval_set(time(NULL)+30, 0);
+#else
+ t = timeval_set(KRB5_EVENT_REFRESH_TIME(ticket_end), 0);
+#endif
+ if (!entry->refresh_time) {
+ entry->refresh_time = t.tv_sec;
+ }
+ entry->event = event_add_timed(winbind_event_context(),
+ entry,
+ t,
+ "krb5_ticket_refresh_handler",
+ krb5_ticket_refresh_handler,
+ entry);
+ }
+
+ if (!entry->event) {
+ ntret = remove_ccache(username);
+ if (!NT_STATUS_IS_OK(ntret)) {
+ DEBUG(0, ("add_ccache_to_list: Failed to remove krb5 "
+ "ccache %s for user %s\n", entry->ccname,
+ entry->username));
+ DEBUG(0, ("add_ccache_to_list: error is %s\n",
+ nt_errstr(ntret)));
+ return ntret;
+ }
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(10,("add_ccache_to_list: added krb5_ticket handler\n"));
+ }
+
return NT_STATUS_OK;
}
entry->ref_count = 1;
if (lp_winbind_refresh_tickets() && renew_until > 0) {
+ struct timeval t;
if (postponed_request) {
- entry->event = event_add_timed(winbind_event_context(), entry,
- timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0),
- "krb5_ticket_gain_handler",
- krb5_ticket_gain_handler,
- entry);
+ add_krb5_ticket_gain_handler_event(entry, t);
} else {
/* Renew at 1/2 the ticket expiration time */
- entry->event = event_add_timed(winbind_event_context(), entry,
#if defined(DEBUG_KRB5_TKT_RENEWAL)
- timeval_set(time(NULL)+30, 0),
+ t = timeval_set(time(NULL)+30, 0);
#else
- timeval_set(KRB5_EVENT_REFRESH_TIME(ticket_end), 0),
+ t = timeval_set(KRB5_EVENT_REFRESH_TIME(ticket_end), 0);
#endif
+ if (!entry->refresh_time) {
+ entry->refresh_time = t.tv_sec;
+ }
+ entry->event = event_add_timed(winbind_event_context(), entry,
+ t,
"krb5_ticket_refresh_handler",
krb5_ticket_refresh_handler,
entry);
void *buf, size_t len, void *private_data)
{
struct winbindd_domain *domain;
+ struct winbindd_domain *primary_domain = NULL;
const char *domainname = (const char *)buf;
if (buf == NULL || len == 0) {
return;
}
+ primary_domain = find_our_domain();
+
/* Mark the requested domain offline. */
for (domain = domain_list(); domain; domain = domain->next) {
if (strequal(domain->name, domainname)) {
DEBUG(5,("child_msg_offline: marking %s offline.\n", domain->name));
set_domain_offline(domain);
+ /* we are in the trusted domain, set the primary domain
+ * offline too */
+ if (domain != primary_domain) {
+ set_domain_offline(primary_domain);
+ }
}
}
}
void *buf, size_t len, void *private_data)
{
struct winbindd_domain *domain;
+ struct winbindd_domain *primary_domain = NULL;
const char *domainname = (const char *)buf;
if (buf == NULL || len == 0) {
return;
}
+ primary_domain = find_our_domain();
+
/* Set our global state as online. */
set_global_winbindd_state_online();
DEBUG(5,("child_msg_online: requesting %s to go online.\n", domain->name));
winbindd_flush_negative_conn_cache(domain);
set_domain_online_request(domain);
+
+ /* we can be in trusted domain, which will contact primary domain
+ * we have to bring primary domain online in trusted domain process
+ * see, winbindd_dual_pam_auth() --> winbindd_dual_pam_auth_samlogon()
+ * --> contact_domain = find_our_domain()
+ * */
+ if (domain != primary_domain) {
+ winbindd_flush_negative_conn_cache(primary_domain);
+ set_domain_online_request(primary_domain);
+ }
}
}
}
talloc_destroy(mem_ctx);
}
+bool reinit_after_fork(struct messaging_context *msg_ctx,
+ struct event_context *ev_ctx,
+ bool parent_longlived);
+void ccache_remove_all_after_fork(void);
+
+bool winbindd_reinit_after_fork(const char *logfile)
+{
+ struct winbindd_domain *dom;
+ struct winbindd_child *cl;
+
+ if (!reinit_after_fork(NULL,
+ winbind_event_context(), true)) {
+ DEBUG(0, ("reinit_after_fork failed.\n"));
+ return false;
+ }
+
+ close_conns_after_fork();
+
+ if (!override_logfile && logfile) {
+ lp_set_logfile(logfile);
+ 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);
+ message_deregister(MSG_WINBIND_ONLINESTATUS);
+
+ ccache_remove_all_after_fork();
+
+ for (dom = domain_list(); dom; dom = dom->next) {
+ TALLOC_FREE(dom->check_online_event);
+ }
+
+ for (cl = children; cl; cl = cl->next) {
+ struct winbindd_async_request *request;
+
+ for (request = cl->requests; request; request = request->next) {
+ TALLOC_FREE(request->reply_timeout_event);
+ }
+ TALLOC_FREE(cl->lockout_policy_event);
+ }
+
+ return true;
+}
+
static BOOL fork_domain_child(struct winbindd_child *child)
{
int fdpair[2];
struct winbindd_cli_state state;
- struct winbindd_domain *domain;
struct winbindd_domain *primary_domain = NULL;
if (socketpair(AF_UNIX, SOCK_STREAM, 0, fdpair) != 0) {
close(fdpair[1]);
/* tdb needs special fork handling */
- if (tdb_reopen_all(1) == -1) {
- DEBUG(0,("tdb_reopen_all failed.\n"));
+ if (!winbindd_reinit_after_fork(child->logfilename)) {
+ DEBUG(0, ("winbindd_reinit_after_fork failed.\n"));
_exit(0);
}
- close_conns_after_fork();
-
- if (!override_logfile) {
- 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);
- message_deregister(MSG_WINBIND_ONLINESTATUS);
-
/* The child is ok with online/offline messages now. */
message_unblock();
message_register(MSG_WINBIND_ONLINESTATUS, child_msg_onlinestatus,
NULL);
+ primary_domain = find_our_domain();
+
+ if (primary_domain == NULL) {
+ smb_panic("no primary domain found");
+ }
+
+ /* It doesn't matter if we allow cache login,
+ * try to bring domain online after fork. */
if ( child->domain ) {
child->domain->startup = True;
child->domain->startup_time = time(NULL);
- }
-
- /* Ensure we have no pending check_online events other
- than one for this domain or the primary domain. */
-
- for (domain = domain_list(); domain; domain = domain->next) {
- if (domain->primary) {
- primary_domain = domain;
- }
- if ((domain != child->domain) && !domain->primary) {
- TALLOC_FREE(domain->check_online_event);
+ /* we can be in primary domain or in trusted domain
+ * If we are in trusted domain, set the primary domain
+ * in start-up mode */
+ if (!(child->domain->internal)) {
+ set_domain_online_request(child->domain);
+ if (!(child->domain->primary)) {
+ primary_domain->startup = True;
+ primary_domain->startup_time = time(NULL);
+ set_domain_online_request(primary_domain);
+ }
}
}
- /* Ensure we're not handling an event inherited from
- our parent. */
-
- cancel_named_event(winbind_event_context(),
- "krb5_ticket_refresh_handler");
-
/* We might be in the idmap child...*/
if (child->domain && !(child->domain->internal) &&
lp_winbind_offline_logon()) {