Line data Source code
1 : /*
2 : Unix SMB/CIFS implementation.
3 :
4 : Winbind daemon - krb5 credential cache functions
5 : and in-memory cache functions.
6 :
7 : Copyright (C) Guenther Deschner 2005-2006
8 : Copyright (C) Jeremy Allison 2006
9 :
10 : This program is free software; you can redistribute it and/or modify
11 : it under the terms of the GNU General Public License as published by
12 : the Free Software Foundation; either version 3 of the License, or
13 : (at your option) any later version.
14 :
15 : This program is distributed in the hope that it will be useful,
16 : but WITHOUT ANY WARRANTY; without even the implied warranty of
17 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 : GNU General Public License for more details.
19 :
20 : You should have received a copy of the GNU General Public License
21 : along with this program. If not, see <http://www.gnu.org/licenses/>.
22 : */
23 :
24 : #include "includes.h"
25 : #include "winbindd.h"
26 : #include "../libcli/auth/libcli_auth.h"
27 : #include "smb_krb5.h"
28 : #include "libads/kerberos_proto.h"
29 : #include "lib/global_contexts.h"
30 :
31 : #undef DBGC_CLASS
32 : #define DBGC_CLASS DBGC_WINBIND
33 :
34 : /* uncomment this to do fast debugging on the krb5 ticket renewal event */
35 : #ifdef DEBUG_KRB5_TKT_RENEWAL
36 : #undef DEBUG_KRB5_TKT_RENEWAL
37 : #endif
38 :
39 : #define MAX_CCACHES 100
40 :
41 : static struct WINBINDD_CCACHE_ENTRY *ccache_list;
42 : static void krb5_ticket_gain_handler(struct tevent_context *,
43 : struct tevent_timer *,
44 : struct timeval,
45 : void *);
46 : static void add_krb5_ticket_gain_handler_event(struct WINBINDD_CCACHE_ENTRY *,
47 : struct timeval);
48 :
49 : /* The Krb5 ticket refresh handler should be scheduled
50 : at one-half of the period from now till the tkt
51 : expiration */
52 :
53 0 : static time_t krb5_event_refresh_time(time_t end_time)
54 : {
55 0 : time_t rest = end_time - time(NULL);
56 0 : return end_time - rest/2;
57 : }
58 :
59 : /****************************************************************
60 : Find an entry by name.
61 : ****************************************************************/
62 :
63 72 : static struct WINBINDD_CCACHE_ENTRY *get_ccache_by_username(const char *username)
64 : {
65 : struct WINBINDD_CCACHE_ENTRY *entry;
66 :
67 72 : for (entry = ccache_list; entry; entry = entry->next) {
68 0 : if (strequal(entry->username, username)) {
69 0 : return entry;
70 : }
71 : }
72 72 : return NULL;
73 : }
74 :
75 : /****************************************************************
76 : How many do we have ?
77 : ****************************************************************/
78 :
79 0 : static int ccache_entry_count(void)
80 : {
81 : struct WINBINDD_CCACHE_ENTRY *entry;
82 0 : int i = 0;
83 :
84 0 : for (entry = ccache_list; entry; entry = entry->next) {
85 0 : i++;
86 : }
87 0 : return i;
88 : }
89 :
90 2 : void ccache_remove_all_after_fork(void)
91 : {
92 : struct WINBINDD_CCACHE_ENTRY *cur, *next;
93 :
94 2 : for (cur = ccache_list; cur; cur = next) {
95 0 : next = cur->next;
96 0 : DLIST_REMOVE(ccache_list, cur);
97 0 : TALLOC_FREE(cur->event);
98 0 : TALLOC_FREE(cur);
99 : }
100 :
101 2 : return;
102 : }
103 :
104 : /****************************************************************
105 : Do the work of refreshing the ticket.
106 : ****************************************************************/
107 :
108 0 : static void krb5_ticket_refresh_handler(struct tevent_context *event_ctx,
109 : struct tevent_timer *te,
110 : struct timeval now,
111 : void *private_data)
112 : {
113 0 : struct WINBINDD_CCACHE_ENTRY *entry =
114 0 : talloc_get_type_abort(private_data, struct WINBINDD_CCACHE_ENTRY);
115 : #ifdef HAVE_KRB5
116 : int ret;
117 : time_t new_start;
118 0 : time_t expire_time = 0;
119 0 : struct WINBINDD_MEMORY_CREDS *cred_ptr = entry->cred_ptr;
120 : #endif
121 :
122 0 : DBG_DEBUG("event called for: %s, %s\n",
123 : entry->ccname, entry->username);
124 :
125 0 : TALLOC_FREE(entry->event);
126 :
127 : #ifdef HAVE_KRB5
128 :
129 : /* Kinit again if we have the user password and we can't renew the old
130 : * tgt anymore
131 : * NB
132 : * This happens when machine are put to sleep for a very long time. */
133 :
134 0 : if (entry->renew_until < time(NULL)) {
135 0 : rekinit:
136 0 : if (cred_ptr && cred_ptr->pass) {
137 :
138 0 : set_effective_uid(entry->uid);
139 :
140 0 : ret = kerberos_kinit_password_ext(entry->principal_name,
141 0 : cred_ptr->pass,
142 : 0, /* hm, can we do time correction here ? */
143 : &entry->refresh_time,
144 : &entry->renew_until,
145 : entry->ccname,
146 : False, /* no PAC required anymore */
147 : True,
148 : WINBINDD_PAM_AUTH_KRB5_RENEW_TIME,
149 : NULL,
150 : NULL,
151 : NULL,
152 : NULL);
153 0 : gain_root_privilege();
154 :
155 0 : if (ret) {
156 0 : DEBUG(3,("krb5_ticket_refresh_handler: "
157 : "could not re-kinit: %s\n",
158 : error_message(ret)));
159 : /* destroy the ticket because we cannot rekinit
160 : * it, ignore error here */
161 0 : ads_kdestroy(entry->ccname);
162 :
163 : /* Don't break the ticket refresh chain: retry
164 : * refreshing ticket sometime later when KDC is
165 : * unreachable -- BoYang. More error code handling
166 : * here?
167 : * */
168 :
169 0 : if ((ret == KRB5_KDC_UNREACH)
170 0 : || (ret == KRB5_REALM_CANT_RESOLVE)) {
171 : #if defined(DEBUG_KRB5_TKT_RENEWAL)
172 : new_start = time(NULL) + 30;
173 : #else
174 0 : new_start = time(NULL) +
175 0 : MAX(30, lp_winbind_cache_time());
176 : #endif
177 0 : add_krb5_ticket_gain_handler_event(entry,
178 : timeval_set(new_start, 0));
179 0 : return;
180 : }
181 0 : TALLOC_FREE(entry->event);
182 0 : return;
183 : }
184 :
185 0 : DEBUG(10,("krb5_ticket_refresh_handler: successful re-kinit "
186 : "for: %s in ccache: %s\n",
187 : entry->principal_name, entry->ccname));
188 :
189 : #if defined(DEBUG_KRB5_TKT_RENEWAL)
190 : new_start = time(NULL) + 30;
191 : #else
192 : /* The tkt should be refreshed at one-half the period
193 : from now to the expiration time */
194 0 : expire_time = entry->refresh_time;
195 0 : new_start = krb5_event_refresh_time(entry->refresh_time);
196 : #endif
197 0 : goto done;
198 : } else {
199 : /* can this happen?
200 : * No cached credentials
201 : * destroy ticket and refresh chain
202 : * */
203 0 : ads_kdestroy(entry->ccname);
204 0 : TALLOC_FREE(entry->event);
205 0 : return;
206 : }
207 : }
208 :
209 0 : set_effective_uid(entry->uid);
210 :
211 0 : ret = smb_krb5_renew_ticket(entry->ccname,
212 : entry->canon_principal,
213 : entry->service,
214 : &new_start);
215 : #if defined(DEBUG_KRB5_TKT_RENEWAL)
216 : new_start = time(NULL) + 30;
217 : #else
218 0 : expire_time = new_start;
219 0 : new_start = krb5_event_refresh_time(new_start);
220 : #endif
221 :
222 0 : gain_root_privilege();
223 :
224 0 : if (ret) {
225 0 : DEBUG(3,("krb5_ticket_refresh_handler: "
226 : "could not renew tickets: %s\n",
227 : error_message(ret)));
228 : /* maybe we are beyond the renewing window */
229 :
230 : /* evil rises here, we refresh ticket failed,
231 : * but the ticket might be expired. Therefore,
232 : * When we refresh ticket failed, destory the
233 : * ticket */
234 :
235 0 : ads_kdestroy(entry->ccname);
236 :
237 : /* avoid breaking the renewal chain: retry in
238 : * lp_winbind_cache_time() seconds when the KDC was not
239 : * available right now.
240 : * the return code can be KRB5_REALM_CANT_RESOLVE.
241 : * More error code handling here? */
242 :
243 0 : if ((ret == KRB5_KDC_UNREACH)
244 0 : || (ret == KRB5_REALM_CANT_RESOLVE)) {
245 : #if defined(DEBUG_KRB5_TKT_RENEWAL)
246 : new_start = time(NULL) + 30;
247 : #else
248 0 : new_start = time(NULL) +
249 0 : MAX(30, lp_winbind_cache_time());
250 : #endif
251 : /* ticket is destroyed here, we have to regain it
252 : * if it is possible */
253 0 : add_krb5_ticket_gain_handler_event(entry,
254 : timeval_set(new_start, 0));
255 0 : return;
256 : }
257 :
258 : /* This is evil, if the ticket was already expired.
259 : * renew ticket function returns KRB5KRB_AP_ERR_TKT_EXPIRED.
260 : * But there is still a chance that we can rekinit it.
261 : *
262 : * This happens when user login in online mode, and then network
263 : * down or something cause winbind goes offline for a very long time,
264 : * and then goes online again. ticket expired, renew failed.
265 : * This happens when machine are put to sleep for a long time,
266 : * but shorter than entry->renew_util.
267 : * NB
268 : * Looks like the KDC is reachable, we want to rekinit as soon as
269 : * possible instead of waiting some time later. */
270 0 : if ((ret == KRB5KRB_AP_ERR_TKT_EXPIRED)
271 0 : || (ret == KRB5_FCC_NOFILE)) goto rekinit;
272 :
273 0 : return;
274 : }
275 :
276 0 : done:
277 : /* in cases that ticket will be unrenewable soon, we don't try to renew ticket
278 : * but try to regain ticket if it is possible */
279 0 : if (entry->renew_until && expire_time
280 0 : && (entry->renew_until <= expire_time)) {
281 : /* try to regain ticket 10 seconds before expiration */
282 0 : expire_time -= 10;
283 0 : add_krb5_ticket_gain_handler_event(entry,
284 : timeval_set(expire_time, 0));
285 0 : return;
286 : }
287 :
288 0 : if (entry->refresh_time == 0) {
289 0 : entry->refresh_time = new_start;
290 : }
291 0 : entry->event = tevent_add_timer(global_event_context(), entry,
292 : timeval_set(new_start, 0),
293 : krb5_ticket_refresh_handler,
294 : entry);
295 :
296 : #endif
297 : }
298 :
299 : /****************************************************************
300 : Do the work of regaining a ticket when coming from offline auth.
301 : ****************************************************************/
302 :
303 0 : static void krb5_ticket_gain_handler(struct tevent_context *event_ctx,
304 : struct tevent_timer *te,
305 : struct timeval now,
306 : void *private_data)
307 : {
308 0 : struct WINBINDD_CCACHE_ENTRY *entry =
309 0 : talloc_get_type_abort(private_data, struct WINBINDD_CCACHE_ENTRY);
310 : #ifdef HAVE_KRB5
311 : int ret;
312 : struct timeval t;
313 0 : struct WINBINDD_MEMORY_CREDS *cred_ptr = entry->cred_ptr;
314 0 : struct winbindd_domain *domain = NULL;
315 : #endif
316 :
317 0 : DBG_DEBUG("event called for: %s, %s\n",
318 : entry->ccname, entry->username);
319 :
320 0 : TALLOC_FREE(entry->event);
321 :
322 : #ifdef HAVE_KRB5
323 :
324 0 : if (!cred_ptr || !cred_ptr->pass) {
325 0 : DEBUG(10,("krb5_ticket_gain_handler: no memory creds\n"));
326 0 : return;
327 : }
328 :
329 0 : if ((domain = find_domain_from_name(entry->realm)) == NULL) {
330 0 : DEBUG(0,("krb5_ticket_gain_handler: unknown domain\n"));
331 0 : return;
332 : }
333 :
334 0 : if (!domain->online) {
335 0 : goto retry_later;
336 : }
337 :
338 0 : set_effective_uid(entry->uid);
339 :
340 0 : ret = kerberos_kinit_password_ext(entry->principal_name,
341 0 : cred_ptr->pass,
342 : 0, /* hm, can we do time correction here ? */
343 : &entry->refresh_time,
344 : &entry->renew_until,
345 : entry->ccname,
346 : False, /* no PAC required anymore */
347 : True,
348 : WINBINDD_PAM_AUTH_KRB5_RENEW_TIME,
349 : NULL,
350 : NULL,
351 : NULL,
352 : NULL);
353 0 : gain_root_privilege();
354 :
355 0 : if (ret) {
356 0 : DEBUG(3,("krb5_ticket_gain_handler: "
357 : "could not kinit: %s\n",
358 : error_message(ret)));
359 : /* evil. If we cannot do it, destroy any the __maybe__
360 : * __existing__ ticket */
361 0 : ads_kdestroy(entry->ccname);
362 0 : goto retry_later;
363 : }
364 :
365 0 : DEBUG(10,("krb5_ticket_gain_handler: "
366 : "successful kinit for: %s in ccache: %s\n",
367 : entry->principal_name, entry->ccname));
368 :
369 0 : goto got_ticket;
370 :
371 0 : retry_later:
372 :
373 : #if defined(DEBUG_KRB5_TKT_RENEWAL)
374 : t = timeval_set(time(NULL) + 30, 0);
375 : #else
376 0 : t = timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0);
377 : #endif
378 :
379 0 : add_krb5_ticket_gain_handler_event(entry, t);
380 0 : return;
381 :
382 0 : got_ticket:
383 :
384 : #if defined(DEBUG_KRB5_TKT_RENEWAL)
385 : t = timeval_set(time(NULL) + 30, 0);
386 : #else
387 0 : t = timeval_set(krb5_event_refresh_time(entry->refresh_time), 0);
388 : #endif
389 :
390 0 : if (entry->refresh_time == 0) {
391 0 : entry->refresh_time = t.tv_sec;
392 : }
393 0 : entry->event = tevent_add_timer(global_event_context(),
394 : entry,
395 : t,
396 : krb5_ticket_refresh_handler,
397 : entry);
398 :
399 0 : return;
400 : #endif
401 : }
402 :
403 : /**************************************************************
404 : The gain initial ticket case is recognised as entry->refresh_time
405 : is always zero.
406 : **************************************************************/
407 :
408 0 : static void add_krb5_ticket_gain_handler_event(struct WINBINDD_CCACHE_ENTRY *entry,
409 : struct timeval t)
410 : {
411 0 : entry->refresh_time = 0;
412 0 : entry->event = tevent_add_timer(global_event_context(),
413 : entry,
414 : t,
415 : krb5_ticket_gain_handler,
416 : entry);
417 0 : }
418 :
419 4 : void ccache_regain_all_now(void)
420 : {
421 : struct WINBINDD_CCACHE_ENTRY *cur;
422 4 : struct timeval t = timeval_current();
423 :
424 4 : for (cur = ccache_list; cur; cur = cur->next) {
425 : struct tevent_timer *new_event;
426 :
427 : /*
428 : * if refresh_time is 0, we know that the
429 : * the event has the krb5_ticket_gain_handler
430 : */
431 0 : if (cur->refresh_time == 0) {
432 0 : new_event = tevent_add_timer(global_event_context(),
433 : cur,
434 : t,
435 : krb5_ticket_gain_handler,
436 : cur);
437 : } else {
438 0 : new_event = tevent_add_timer(global_event_context(),
439 : cur,
440 : t,
441 : krb5_ticket_refresh_handler,
442 : cur);
443 : }
444 :
445 0 : if (!new_event) {
446 0 : continue;
447 : }
448 :
449 0 : TALLOC_FREE(cur->event);
450 0 : cur->event = new_event;
451 : }
452 :
453 4 : return;
454 : }
455 :
456 : /****************************************************************
457 : Check if an ccache entry exists.
458 : ****************************************************************/
459 :
460 0 : bool ccache_entry_exists(const char *username)
461 : {
462 0 : struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
463 0 : return (entry != NULL);
464 : }
465 :
466 : /****************************************************************
467 : Ensure we're changing the correct entry.
468 : ****************************************************************/
469 :
470 0 : bool ccache_entry_identical(const char *username,
471 : uid_t uid,
472 : const char *ccname)
473 : {
474 0 : struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
475 :
476 0 : if (!entry) {
477 0 : return False;
478 : }
479 :
480 0 : if (entry->uid != uid) {
481 0 : DEBUG(0,("cache_entry_identical: uid's differ: %u != %u\n",
482 : (unsigned int)entry->uid, (unsigned int)uid));
483 0 : return False;
484 : }
485 0 : if (!strcsequal(entry->ccname, ccname)) {
486 0 : DEBUG(0,("cache_entry_identical: "
487 : "ccnames differ: (cache) %s != (client) %s\n",
488 : entry->ccname, ccname));
489 0 : return False;
490 : }
491 0 : return True;
492 : }
493 :
494 0 : NTSTATUS add_ccache_to_list(const char *princ_name,
495 : const char *ccname,
496 : const char *username,
497 : const char *pass,
498 : const char *realm,
499 : uid_t uid,
500 : time_t create_time,
501 : time_t ticket_end,
502 : time_t renew_until,
503 : bool postponed_request,
504 : const char *canon_principal,
505 : const char *canon_realm)
506 : {
507 0 : struct WINBINDD_CCACHE_ENTRY *entry = NULL;
508 : struct timeval t;
509 : NTSTATUS ntret;
510 :
511 0 : if ((username == NULL && princ_name == NULL) ||
512 0 : ccname == NULL || uid == (uid_t)-1) {
513 0 : return NT_STATUS_INVALID_PARAMETER;
514 : }
515 :
516 0 : if (ccache_entry_count() + 1 > MAX_CCACHES) {
517 0 : DEBUG(10,("add_ccache_to_list: "
518 : "max number of ccaches reached\n"));
519 0 : return NT_STATUS_NO_MORE_ENTRIES;
520 : }
521 :
522 : /* Reference count old entries */
523 0 : entry = get_ccache_by_username(username);
524 0 : if (entry) {
525 : /* Check cached entries are identical. */
526 0 : if (!ccache_entry_identical(username, uid, ccname)) {
527 0 : return NT_STATUS_INVALID_PARAMETER;
528 : }
529 0 : entry->ref_count++;
530 0 : DEBUG(10,("add_ccache_to_list: "
531 : "ref count on entry %s is now %d\n",
532 : username, entry->ref_count));
533 : /* FIXME: in this case we still might want to have a krb5 cred
534 : * event handler created - gd
535 : * Add ticket refresh handler here */
536 :
537 0 : if (!lp_winbind_refresh_tickets() || renew_until <= 0) {
538 0 : return NT_STATUS_OK;
539 : }
540 :
541 0 : if (!entry->event) {
542 0 : if (postponed_request) {
543 0 : t = timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0);
544 0 : add_krb5_ticket_gain_handler_event(entry, t);
545 : } else {
546 : /* Renew at 1/2 the ticket expiration time */
547 : #if defined(DEBUG_KRB5_TKT_RENEWAL)
548 : t = timeval_set(time(NULL)+30, 0);
549 : #else
550 0 : t = timeval_set(krb5_event_refresh_time(ticket_end),
551 : 0);
552 : #endif
553 0 : if (!entry->refresh_time) {
554 0 : entry->refresh_time = t.tv_sec;
555 : }
556 0 : entry->event = tevent_add_timer(global_event_context(),
557 : entry,
558 : t,
559 : krb5_ticket_refresh_handler,
560 : entry);
561 : }
562 :
563 0 : if (!entry->event) {
564 0 : ntret = remove_ccache(username);
565 0 : if (!NT_STATUS_IS_OK(ntret)) {
566 0 : DEBUG(0, ("add_ccache_to_list: Failed to remove krb5 "
567 : "ccache %s for user %s\n", entry->ccname,
568 : entry->username));
569 0 : DEBUG(0, ("add_ccache_to_list: error is %s\n",
570 : nt_errstr(ntret)));
571 0 : return ntret;
572 : }
573 0 : return NT_STATUS_NO_MEMORY;
574 : }
575 :
576 0 : DEBUG(10,("add_ccache_to_list: added krb5_ticket handler\n"));
577 :
578 : }
579 :
580 : /*
581 : * If we're set up to renew our krb5 tickets, we must
582 : * cache the credentials in memory for the ticket
583 : * renew function (or increase the reference count
584 : * if we're logging in more than once). Fix inspired
585 : * by patch from Ian Gordon <ian.gordon@strath.ac.uk>
586 : * for bugid #9098.
587 : */
588 :
589 0 : ntret = winbindd_add_memory_creds(username, uid, pass);
590 0 : DEBUG(10, ("winbindd_add_memory_creds returned: %s\n",
591 : nt_errstr(ntret)));
592 :
593 0 : return NT_STATUS_OK;
594 : }
595 :
596 0 : entry = talloc(NULL, struct WINBINDD_CCACHE_ENTRY);
597 0 : if (!entry) {
598 0 : return NT_STATUS_NO_MEMORY;
599 : }
600 :
601 0 : ZERO_STRUCTP(entry);
602 :
603 0 : if (username) {
604 0 : entry->username = talloc_strdup(entry, username);
605 0 : if (!entry->username) {
606 0 : goto no_mem;
607 : }
608 : }
609 0 : if (princ_name) {
610 0 : entry->principal_name = talloc_strdup(entry, princ_name);
611 0 : if (!entry->principal_name) {
612 0 : goto no_mem;
613 : }
614 : }
615 0 : if (canon_principal != NULL) {
616 0 : entry->canon_principal = talloc_strdup(entry, canon_principal);
617 0 : if (entry->canon_principal == NULL) {
618 0 : goto no_mem;
619 : }
620 : }
621 0 : if (canon_realm != NULL) {
622 0 : entry->canon_realm = talloc_strdup(entry, canon_realm);
623 0 : if (entry->canon_realm == NULL) {
624 0 : goto no_mem;
625 : }
626 : }
627 :
628 0 : entry->ccname = talloc_strdup(entry, ccname);
629 0 : if (!entry->ccname) {
630 0 : goto no_mem;
631 : }
632 :
633 0 : entry->realm = talloc_strdup(entry, realm);
634 0 : if (!entry->realm) {
635 0 : goto no_mem;
636 : }
637 :
638 0 : entry->service = talloc_asprintf(entry,
639 : "%s/%s@%s",
640 : KRB5_TGS_NAME,
641 : canon_realm,
642 : canon_realm);
643 0 : if (entry->service == NULL) {
644 0 : goto no_mem;
645 : }
646 :
647 0 : entry->create_time = create_time;
648 0 : entry->renew_until = renew_until;
649 0 : entry->uid = uid;
650 0 : entry->ref_count = 1;
651 :
652 0 : if (!lp_winbind_refresh_tickets() || renew_until <= 0) {
653 0 : goto add_entry;
654 : }
655 :
656 0 : if (postponed_request) {
657 0 : t = timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0);
658 0 : add_krb5_ticket_gain_handler_event(entry, t);
659 : } else {
660 : /* Renew at 1/2 the ticket expiration time */
661 : #if defined(DEBUG_KRB5_TKT_RENEWAL)
662 : t = timeval_set(time(NULL)+30, 0);
663 : #else
664 0 : t = timeval_set(krb5_event_refresh_time(ticket_end), 0);
665 : #endif
666 0 : if (entry->refresh_time == 0) {
667 0 : entry->refresh_time = t.tv_sec;
668 : }
669 0 : entry->event = tevent_add_timer(global_event_context(),
670 : entry,
671 : t,
672 : krb5_ticket_refresh_handler,
673 : entry);
674 : }
675 :
676 0 : if (!entry->event) {
677 0 : goto no_mem;
678 : }
679 :
680 0 : DEBUG(10,("add_ccache_to_list: added krb5_ticket handler\n"));
681 :
682 0 : add_entry:
683 :
684 0 : DLIST_ADD(ccache_list, entry);
685 :
686 0 : DBG_DEBUG("Added ccache [%s] for user [%s] and service [%s]\n",
687 : entry->ccname, entry->username, entry->service);
688 :
689 0 : if (entry->event) {
690 : /*
691 : * If we're set up to renew our krb5 tickets, we must
692 : * cache the credentials in memory for the ticket
693 : * renew function. Fix inspired by patch from
694 : * Ian Gordon <ian.gordon@strath.ac.uk> for
695 : * bugid #9098.
696 : */
697 :
698 0 : ntret = winbindd_add_memory_creds(username, uid, pass);
699 0 : DEBUG(10, ("winbindd_add_memory_creds returned: %s\n",
700 : nt_errstr(ntret)));
701 : }
702 :
703 0 : return NT_STATUS_OK;
704 :
705 0 : no_mem:
706 :
707 0 : TALLOC_FREE(entry);
708 0 : return NT_STATUS_NO_MEMORY;
709 : }
710 :
711 : /*******************************************************************
712 : Remove a WINBINDD_CCACHE_ENTRY entry and the krb5 ccache if no longer
713 : referenced.
714 : *******************************************************************/
715 :
716 0 : NTSTATUS remove_ccache(const char *username)
717 : {
718 0 : struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
719 0 : NTSTATUS status = NT_STATUS_OK;
720 : #ifdef HAVE_KRB5
721 : krb5_error_code ret;
722 : #endif
723 :
724 0 : if (!entry) {
725 0 : return NT_STATUS_OBJECT_NAME_NOT_FOUND;
726 : }
727 :
728 0 : if (entry->ref_count <= 0) {
729 0 : DEBUG(0,("remove_ccache: logic error. "
730 : "ref count for user %s = %d\n",
731 : username, entry->ref_count));
732 0 : return NT_STATUS_INTERNAL_DB_CORRUPTION;
733 : }
734 :
735 0 : entry->ref_count--;
736 :
737 0 : if (entry->ref_count > 0) {
738 0 : DEBUG(10,("remove_ccache: entry %s ref count now %d\n",
739 : username, entry->ref_count));
740 0 : return NT_STATUS_OK;
741 : }
742 :
743 : /* no references any more */
744 :
745 0 : DLIST_REMOVE(ccache_list, entry);
746 0 : TALLOC_FREE(entry->event); /* unregisters events */
747 :
748 : #ifdef HAVE_KRB5
749 0 : ret = ads_kdestroy(entry->ccname);
750 :
751 : /* we ignore the error when there has been no credential cache */
752 0 : if (ret == KRB5_FCC_NOFILE) {
753 0 : ret = 0;
754 0 : } else if (ret) {
755 0 : DEBUG(0,("remove_ccache: "
756 : "failed to destroy user krb5 ccache %s with: %s\n",
757 : entry->ccname, error_message(ret)));
758 : } else {
759 0 : DEBUG(10,("remove_ccache: "
760 : "successfully destroyed krb5 ccache %s for user %s\n",
761 : entry->ccname, username));
762 : }
763 0 : status = krb5_to_nt_status(ret);
764 : #endif
765 :
766 0 : TALLOC_FREE(entry);
767 0 : DEBUG(10,("remove_ccache: removed ccache for user %s\n", username));
768 :
769 0 : return status;
770 : }
771 :
772 : /*******************************************************************
773 : In memory credentials cache code.
774 : *******************************************************************/
775 :
776 : static struct WINBINDD_MEMORY_CREDS *memory_creds_list;
777 :
778 : /***********************************************************
779 : Find an entry on the list by name.
780 : ***********************************************************/
781 :
782 88 : struct WINBINDD_MEMORY_CREDS *find_memory_creds_by_name(const char *username)
783 : {
784 : struct WINBINDD_MEMORY_CREDS *p;
785 :
786 153 : for (p = memory_creds_list; p; p = p->next) {
787 112 : if (strequal(p->username, username)) {
788 47 : return p;
789 : }
790 : }
791 41 : return NULL;
792 : }
793 :
794 : /***********************************************************
795 : Store the required creds and mlock them.
796 : ***********************************************************/
797 :
798 68 : static NTSTATUS store_memory_creds(struct WINBINDD_MEMORY_CREDS *memcredp,
799 : const char *pass)
800 : {
801 : #if !defined(HAVE_MLOCK)
802 : return NT_STATUS_OK;
803 : #else
804 : /* new_entry->nt_hash is the base pointer for the block
805 : of memory pointed into by new_entry->lm_hash and
806 : new_entry->pass (if we're storing plaintext). */
807 :
808 68 : memcredp->len = NT_HASH_LEN + LM_HASH_LEN;
809 68 : if (pass) {
810 68 : memcredp->len += strlen(pass)+1;
811 : }
812 :
813 :
814 : #if defined(LINUX)
815 : /* aligning the memory on on x86_64 and compiling
816 : with gcc 4.1 using -O2 causes a segv in the
817 : next memset() --jerry */
818 68 : memcredp->nt_hash = SMB_MALLOC_ARRAY(unsigned char, memcredp->len);
819 : #else
820 : /* On non-linux platforms, mlock()'d memory must be aligned */
821 : memcredp->nt_hash = SMB_MEMALIGN_ARRAY(unsigned char,
822 : getpagesize(), memcredp->len);
823 : #endif
824 68 : if (!memcredp->nt_hash) {
825 0 : return NT_STATUS_NO_MEMORY;
826 : }
827 68 : memset(memcredp->nt_hash, 0x0, memcredp->len);
828 :
829 68 : memcredp->lm_hash = memcredp->nt_hash + NT_HASH_LEN;
830 :
831 : #ifdef DEBUG_PASSWORD
832 68 : DEBUG(10,("mlocking memory: %p\n", memcredp->nt_hash));
833 : #endif
834 68 : if ((mlock(memcredp->nt_hash, memcredp->len)) == -1) {
835 0 : DEBUG(0,("failed to mlock memory: %s (%d)\n",
836 : strerror(errno), errno));
837 0 : SAFE_FREE(memcredp->nt_hash);
838 0 : return map_nt_error_from_unix(errno);
839 : }
840 :
841 : #ifdef DEBUG_PASSWORD
842 68 : DEBUG(10,("mlocked memory: %p\n", memcredp->nt_hash));
843 : #endif
844 :
845 68 : if (pass) {
846 : /* Create and store the password hashes. */
847 68 : E_md4hash(pass, memcredp->nt_hash);
848 68 : E_deshash(pass, memcredp->lm_hash);
849 :
850 68 : memcredp->pass = (char *)memcredp->lm_hash + LM_HASH_LEN;
851 68 : memcpy(memcredp->pass, pass,
852 68 : memcredp->len - NT_HASH_LEN - LM_HASH_LEN);
853 : }
854 :
855 68 : return NT_STATUS_OK;
856 : #endif
857 : }
858 :
859 : /***********************************************************
860 : Destroy existing creds.
861 : ***********************************************************/
862 :
863 31 : static NTSTATUS delete_memory_creds(struct WINBINDD_MEMORY_CREDS *memcredp)
864 : {
865 : #if !defined(HAVE_MUNLOCK)
866 : return NT_STATUS_OK;
867 : #else
868 31 : if (munlock(memcredp->nt_hash, memcredp->len) == -1) {
869 0 : DEBUG(0,("failed to munlock memory: %s (%d)\n",
870 : strerror(errno), errno));
871 0 : return map_nt_error_from_unix(errno);
872 : }
873 31 : memset(memcredp->nt_hash, '\0', memcredp->len);
874 31 : SAFE_FREE(memcredp->nt_hash);
875 31 : memcredp->nt_hash = NULL;
876 31 : memcredp->lm_hash = NULL;
877 31 : memcredp->pass = NULL;
878 31 : memcredp->len = 0;
879 31 : return NT_STATUS_OK;
880 : #endif
881 : }
882 :
883 : /***********************************************************
884 : Replace the required creds with new ones (password change).
885 : ***********************************************************/
886 :
887 31 : static NTSTATUS winbindd_replace_memory_creds_internal(struct WINBINDD_MEMORY_CREDS *memcredp,
888 : const char *pass)
889 : {
890 31 : NTSTATUS status = delete_memory_creds(memcredp);
891 31 : if (!NT_STATUS_IS_OK(status)) {
892 0 : return status;
893 : }
894 31 : return store_memory_creds(memcredp, pass);
895 : }
896 :
897 : /*************************************************************
898 : Store credentials in memory in a list.
899 : *************************************************************/
900 :
901 68 : static NTSTATUS winbindd_add_memory_creds_internal(const char *username,
902 : uid_t uid,
903 : const char *pass)
904 : {
905 : /* Shortcut to ensure we don't store if no mlock. */
906 : #if !defined(HAVE_MLOCK) || !defined(HAVE_MUNLOCK)
907 : return NT_STATUS_OK;
908 : #else
909 : NTSTATUS status;
910 68 : struct WINBINDD_MEMORY_CREDS *memcredp = NULL;
911 :
912 68 : memcredp = find_memory_creds_by_name(username);
913 68 : if (uid == (uid_t)-1) {
914 0 : DEBUG(0,("winbindd_add_memory_creds_internal: "
915 : "invalid uid for user %s.\n", username));
916 0 : return NT_STATUS_INVALID_PARAMETER;
917 : }
918 :
919 68 : if (memcredp) {
920 : /* Already exists. Increment the reference count and replace stored creds. */
921 31 : if (uid != memcredp->uid) {
922 0 : DEBUG(0,("winbindd_add_memory_creds_internal: "
923 : "uid %u for user %s doesn't "
924 : "match stored uid %u. Replacing.\n",
925 : (unsigned int)uid, username,
926 : (unsigned int)memcredp->uid));
927 0 : memcredp->uid = uid;
928 : }
929 31 : memcredp->ref_count++;
930 31 : DEBUG(10,("winbindd_add_memory_creds_internal: "
931 : "ref count for user %s is now %d\n",
932 : username, memcredp->ref_count));
933 31 : return winbindd_replace_memory_creds_internal(memcredp, pass);
934 : }
935 :
936 37 : memcredp = talloc_zero(NULL, struct WINBINDD_MEMORY_CREDS);
937 37 : if (!memcredp) {
938 0 : return NT_STATUS_NO_MEMORY;
939 : }
940 37 : memcredp->username = talloc_strdup(memcredp, username);
941 37 : if (!memcredp->username) {
942 0 : talloc_destroy(memcredp);
943 0 : return NT_STATUS_NO_MEMORY;
944 : }
945 :
946 37 : status = store_memory_creds(memcredp, pass);
947 37 : if (!NT_STATUS_IS_OK(status)) {
948 0 : talloc_destroy(memcredp);
949 0 : return status;
950 : }
951 :
952 37 : memcredp->uid = uid;
953 37 : memcredp->ref_count = 1;
954 37 : DLIST_ADD(memory_creds_list, memcredp);
955 :
956 37 : DEBUG(10,("winbindd_add_memory_creds_internal: "
957 : "added entry for user %s\n", username));
958 :
959 37 : return NT_STATUS_OK;
960 : #endif
961 : }
962 :
963 : /*************************************************************
964 : Store users credentials in memory. If we also have a
965 : struct WINBINDD_CCACHE_ENTRY for this username with a
966 : refresh timer, then store the plaintext of the password
967 : and associate the new credentials with the struct WINBINDD_CCACHE_ENTRY.
968 : *************************************************************/
969 :
970 68 : NTSTATUS winbindd_add_memory_creds(const char *username,
971 : uid_t uid,
972 : const char *pass)
973 : {
974 68 : struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
975 : NTSTATUS status;
976 :
977 68 : status = winbindd_add_memory_creds_internal(username, uid, pass);
978 68 : if (!NT_STATUS_IS_OK(status)) {
979 0 : return status;
980 : }
981 :
982 68 : if (entry) {
983 0 : struct WINBINDD_MEMORY_CREDS *memcredp = NULL;
984 0 : memcredp = find_memory_creds_by_name(username);
985 0 : if (memcredp) {
986 0 : entry->cred_ptr = memcredp;
987 : }
988 : }
989 :
990 68 : return status;
991 : }
992 :
993 : /*************************************************************
994 : Decrement the in-memory ref count - delete if zero.
995 : *************************************************************/
996 :
997 4 : NTSTATUS winbindd_delete_memory_creds(const char *username)
998 : {
999 4 : struct WINBINDD_MEMORY_CREDS *memcredp = NULL;
1000 4 : struct WINBINDD_CCACHE_ENTRY *entry = NULL;
1001 4 : NTSTATUS status = NT_STATUS_OK;
1002 :
1003 4 : memcredp = find_memory_creds_by_name(username);
1004 4 : entry = get_ccache_by_username(username);
1005 :
1006 4 : if (!memcredp) {
1007 4 : DEBUG(10,("winbindd_delete_memory_creds: unknown user %s\n",
1008 : username));
1009 4 : return NT_STATUS_OBJECT_NAME_NOT_FOUND;
1010 : }
1011 :
1012 0 : if (memcredp->ref_count <= 0) {
1013 0 : DEBUG(0,("winbindd_delete_memory_creds: logic error. "
1014 : "ref count for user %s = %d\n",
1015 : username, memcredp->ref_count));
1016 0 : status = NT_STATUS_INTERNAL_DB_CORRUPTION;
1017 : }
1018 :
1019 0 : memcredp->ref_count--;
1020 0 : if (memcredp->ref_count <= 0) {
1021 0 : delete_memory_creds(memcredp);
1022 0 : DLIST_REMOVE(memory_creds_list, memcredp);
1023 0 : talloc_destroy(memcredp);
1024 0 : DEBUG(10,("winbindd_delete_memory_creds: "
1025 : "deleted entry for user %s\n",
1026 : username));
1027 : } else {
1028 0 : DEBUG(10,("winbindd_delete_memory_creds: "
1029 : "entry for user %s ref_count now %d\n",
1030 : username, memcredp->ref_count));
1031 : }
1032 :
1033 0 : if (entry) {
1034 : /* Ensure we have no dangling references to this. */
1035 0 : entry->cred_ptr = NULL;
1036 : }
1037 :
1038 0 : return status;
1039 : }
1040 :
1041 : /***********************************************************
1042 : Replace the required creds with new ones (password change).
1043 : ***********************************************************/
1044 :
1045 0 : NTSTATUS winbindd_replace_memory_creds(const char *username,
1046 : const char *pass)
1047 : {
1048 0 : struct WINBINDD_MEMORY_CREDS *memcredp = NULL;
1049 :
1050 0 : memcredp = find_memory_creds_by_name(username);
1051 0 : if (!memcredp) {
1052 0 : DEBUG(10,("winbindd_replace_memory_creds: unknown user %s\n",
1053 : username));
1054 0 : return NT_STATUS_OBJECT_NAME_NOT_FOUND;
1055 : }
1056 :
1057 0 : DEBUG(10,("winbindd_replace_memory_creds: replaced creds for user %s\n",
1058 : username));
1059 :
1060 0 : return winbindd_replace_memory_creds_internal(memcredp, pass);
1061 : }
|