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