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

Generated by: LCOV version 1.14