root/source3/winbindd/winbindd_cred_cache.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. get_ccache_by_username
  2. ccache_entry_count
  3. ccache_remove_all_after_fork
  4. krb5_ticket_refresh_handler
  5. krb5_ticket_gain_handler
  6. add_krb5_ticket_gain_handler_event
  7. ccache_regain_all_now
  8. ccache_entry_exists
  9. ccache_entry_identical
  10. add_ccache_to_list
  11. remove_ccache
  12. find_memory_creds_by_name
  13. store_memory_creds
  14. delete_memory_creds
  15. winbindd_replace_memory_creds_internal
  16. winbindd_add_memory_creds_internal
  17. winbindd_add_memory_creds
  18. winbindd_delete_memory_creds
  19. winbindd_replace_memory_creds

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

/* [<][>][^][v][top][bottom][index][help] */