LCOV - code coverage report
Current view: top level - source3/winbindd - winbindd_cred_cache.c (source / functions) Hit Total Coverage
Test: coverage report for master 2b515b7d Lines: 80 384 20.8 %
Date: 2024-02-28 12:06:22 Functions: 8 20 40.0 %

          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(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, destroy 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           0 :         int ret;
     312           0 :         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           0 : void ccache_regain_all_now(void)
     420             : {
     421           0 :         struct WINBINDD_CCACHE_ENTRY *cur;
     422           0 :         struct timeval t = timeval_current();
     423             : 
     424           0 :         for (cur = ccache_list; cur; cur = cur->next) {
     425           0 :                 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           0 :         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           0 :         struct timeval t;
     509           0 :         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           0 :         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         174 : struct WINBINDD_MEMORY_CREDS *find_memory_creds_by_name(const char *username)
     783             : {
     784           0 :         struct WINBINDD_MEMORY_CREDS *p;
     785             : 
     786         242 :         for (p = memory_creds_list; p; p = p->next) {
     787         198 :                 if (strequal(p->username, username)) {
     788         130 :                         return p;
     789             :                 }
     790             :         }
     791          44 :         return NULL;
     792             : }
     793             : 
     794             : /***********************************************************
     795             :  Store the required creds and mlock them.
     796             : ***********************************************************/
     797             : 
     798          98 : 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          98 :         memcredp->len = NT_HASH_LEN + LM_HASH_LEN;
     809          98 :         if (pass) {
     810          98 :                 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          98 :         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          98 :         if (!memcredp->nt_hash) {
     825           0 :                 return NT_STATUS_NO_MEMORY;
     826             :         }
     827          98 :         memset(memcredp->nt_hash, 0x0, memcredp->len);
     828             : 
     829          98 :         memcredp->lm_hash = memcredp->nt_hash + NT_HASH_LEN;
     830             : 
     831             : #ifdef DEBUG_PASSWORD
     832          98 :         DEBUG(10,("mlocking memory: %p\n", memcredp->nt_hash));
     833             : #endif
     834          98 :         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          98 :         DEBUG(10,("mlocked memory: %p\n", memcredp->nt_hash));
     843             : #endif
     844             : 
     845          98 :         if (pass) {
     846             :                 /* Create and store the password hashes. */
     847          98 :                 E_md4hash(pass, memcredp->nt_hash);
     848          98 :                 E_deshash(pass, memcredp->lm_hash);
     849             : 
     850          98 :                 memcredp->pass = (char *)memcredp->lm_hash + LM_HASH_LEN;
     851          98 :                 memcpy(memcredp->pass, pass,
     852          98 :                        memcredp->len - NT_HASH_LEN - LM_HASH_LEN);
     853             :         }
     854             : 
     855          98 :         return NT_STATUS_OK;
     856             : #endif
     857             : }
     858             : 
     859             : /***********************************************************
     860             :  Destroy existing creds.
     861             : ***********************************************************/
     862             : 
     863          62 : static NTSTATUS delete_memory_creds(struct WINBINDD_MEMORY_CREDS *memcredp)
     864             : {
     865             : #if !defined(HAVE_MUNLOCK)
     866             :         return NT_STATUS_OK;
     867             : #else
     868          62 :         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          62 :         memset(memcredp->nt_hash, '\0', memcredp->len);
     874          62 :         SAFE_FREE(memcredp->nt_hash);
     875          62 :         memcredp->nt_hash = NULL;
     876          62 :         memcredp->lm_hash = NULL;
     877          62 :         memcredp->pass = NULL;
     878          62 :         memcredp->len = 0;
     879          62 :         return NT_STATUS_OK;
     880             : #endif
     881             : }
     882             : 
     883             : /***********************************************************
     884             :  Replace the required creds with new ones (password change).
     885             : ***********************************************************/
     886             : 
     887          62 : static NTSTATUS winbindd_replace_memory_creds_internal(struct WINBINDD_MEMORY_CREDS *memcredp,
     888             :                                                        const char *pass)
     889             : {
     890          62 :         NTSTATUS status = delete_memory_creds(memcredp);
     891          62 :         if (!NT_STATUS_IS_OK(status)) {
     892           0 :                 return status;
     893             :         }
     894          62 :         return store_memory_creds(memcredp, pass);
     895             : }
     896             : 
     897             : /*************************************************************
     898             :  Store credentials in memory in a list.
     899             : *************************************************************/
     900             : 
     901          98 : 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           0 :         NTSTATUS status;
     910          98 :         struct WINBINDD_MEMORY_CREDS *memcredp = NULL;
     911             : 
     912          98 :         memcredp = find_memory_creds_by_name(username);
     913          98 :         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          98 :         if (memcredp) {
     920             :                 /* Already exists. Increment the reference count and replace stored creds. */
     921          62 :                 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          62 :                 memcredp->ref_count++;
     930          62 :                 DEBUG(10,("winbindd_add_memory_creds_internal: "
     931             :                         "ref count for user %s is now %d\n",
     932             :                         username, memcredp->ref_count));
     933          62 :                 return winbindd_replace_memory_creds_internal(memcredp, pass);
     934             :         }
     935             : 
     936          36 :         memcredp = talloc_zero(NULL, struct WINBINDD_MEMORY_CREDS);
     937          36 :         if (!memcredp) {
     938           0 :                 return NT_STATUS_NO_MEMORY;
     939             :         }
     940          36 :         memcredp->username = talloc_strdup(memcredp, username);
     941          36 :         if (!memcredp->username) {
     942           0 :                 talloc_destroy(memcredp);
     943           0 :                 return NT_STATUS_NO_MEMORY;
     944             :         }
     945             : 
     946          36 :         status = store_memory_creds(memcredp, pass);
     947          36 :         if (!NT_STATUS_IS_OK(status)) {
     948           0 :                 talloc_destroy(memcredp);
     949           0 :                 return status;
     950             :         }
     951             : 
     952          36 :         memcredp->uid = uid;
     953          36 :         memcredp->ref_count = 1;
     954          36 :         DLIST_ADD(memory_creds_list, memcredp);
     955             : 
     956          36 :         DEBUG(10,("winbindd_add_memory_creds_internal: "
     957             :                 "added entry for user %s\n", username));
     958             : 
     959          36 :         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          98 : NTSTATUS winbindd_add_memory_creds(const char *username,
     971             :                                    uid_t uid,
     972             :                                    const char *pass)
     973             : {
     974          98 :         struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
     975           0 :         NTSTATUS status;
     976             : 
     977          98 :         status = winbindd_add_memory_creds_internal(username, uid, pass);
     978          98 :         if (!NT_STATUS_IS_OK(status)) {
     979           0 :                 return status;
     980             :         }
     981             : 
     982          98 :         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          98 :         return status;
     991             : }
     992             : 
     993             : /*************************************************************
     994             :  Decrement the in-memory ref count - delete if zero.
     995             : *************************************************************/
     996             : 
     997          20 : NTSTATUS winbindd_delete_memory_creds(const char *username)
     998             : {
     999          20 :         struct WINBINDD_MEMORY_CREDS *memcredp = NULL;
    1000          20 :         struct WINBINDD_CCACHE_ENTRY *entry = NULL;
    1001          20 :         NTSTATUS status = NT_STATUS_OK;
    1002             : 
    1003          20 :         memcredp = find_memory_creds_by_name(username);
    1004          20 :         entry = get_ccache_by_username(username);
    1005             : 
    1006          20 :         if (!memcredp) {
    1007           8 :                 DEBUG(10,("winbindd_delete_memory_creds: unknown user %s\n",
    1008             :                         username));
    1009           8 :                 return NT_STATUS_OBJECT_NAME_NOT_FOUND;
    1010             :         }
    1011             : 
    1012          12 :         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          12 :         memcredp->ref_count--;
    1020          12 :         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          12 :                 DEBUG(10,("winbindd_delete_memory_creds: "
    1029             :                         "entry for user %s ref_count now %d\n",
    1030             :                         username, memcredp->ref_count));
    1031             :         }
    1032             : 
    1033          12 :         if (entry) {
    1034             :                 /* Ensure we have no dangling references to this. */
    1035           0 :                 entry->cred_ptr = NULL;
    1036             :         }
    1037             : 
    1038          12 :         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             : }

Generated by: LCOV version 1.14