/* 
   Unix SMB/Netbios implementation.
   Version 2.0

   Winbind daemon - caching related functions

   Copyright (C) Tim Potter 2000
   
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.
   
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
   
   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "winbindd.h"

#define CACHE_TYPE_USER "USR"
#define CACHE_TYPE_GROUP "GRP"

/* Initialise caching system */

static TDB_CONTEXT *cache_tdb;

struct cache_rec {
	uint32 seq_num;
	time_t mod_time;
};

void winbindd_cache_init(void)
{
	/* Open tdb cache */
	unlink(lock_path("winbindd_cache.tdb"));
	if (!(cache_tdb = tdb_open(lock_path("winbindd_cache.tdb"), 0, 
				   TDB_NOLOCK,
				   O_RDWR | O_CREAT, 0600))) {
		DEBUG(0, ("Unable to open tdb cache - user and group caching "
			  "disabled\n"));
	}
}

/* get the domain sequence number, possibly re-fetching */
static uint32 cached_sequence_number(char *domain_name)
{
	fstring keystr;
	TDB_DATA dbuf;
	struct cache_rec rec;
	time_t t = time(NULL);

	slprintf(keystr, sizeof(keystr), "CACHESEQ/%s", domain_name);
	dos_to_unix(keystr, True);             /* Convert key to unix-codepage */
	dbuf = tdb_fetch_by_string(cache_tdb, keystr);
	if (!dbuf.dptr || dbuf.dsize != sizeof(rec)) {
		goto refetch;
	}
	memcpy(&rec, dbuf.dptr, sizeof(rec));
	free(dbuf.dptr);

	if (t < (rec.mod_time + lp_winbind_cache_time())) {
		DEBUG(4,("cached sequence number for %s is %u\n",
			 domain_name, (unsigned)rec.seq_num));
		return rec.seq_num;
	}

 refetch:	
	rec.seq_num = domain_sequence_number(domain_name);
	rec.mod_time = t;
	tdb_store_by_string(cache_tdb, keystr, &rec, sizeof(rec));

	return rec.seq_num;
}

/* Check whether a seq_num for a cached item has expired */
static BOOL cache_domain_expired(char *domain_name, uint32 seq_num)
{
	if (cached_sequence_number(domain_name) != seq_num) {
		DEBUG(4,("seq %u for %s has expired\n", (unsigned)seq_num, domain_name));
		return True;
	}
	return False;
}

static void set_cache_sequence_number(char *domain_name, char *cache_type, char *subkey)
{
	fstring keystr;
	slprintf(keystr,sizeof(keystr),"CACHESEQ %s/%s/%s",
		 domain_name, cache_type, subkey?subkey:"");
	dos_to_unix(keystr, True);             /* Convert key to unix-codepage */
	tdb_store_int(cache_tdb, keystr, cached_sequence_number(domain_name));
}

static uint32 get_cache_sequence_number(char *domain_name, char *cache_type, char *subkey)
{
	fstring keystr;
	uint32 seq_num;
	slprintf(keystr,sizeof(keystr),"CACHESEQ %s/%s/%s",
		 domain_name, cache_type, subkey?subkey:"");
	dos_to_unix(keystr, True);             /* Convert key to unix-codepage */
	seq_num = (uint32)tdb_fetch_int(cache_tdb, keystr);
	DEBUG(4,("%s is %u\n", keystr, (unsigned)seq_num));
	return seq_num;
}

/* Fill the user or group cache with supplied data */
static void fill_cache(char *domain_name, char *cache_type,
                       struct acct_info *sam_entries,
                       int num_sam_entries)
{
	fstring keystr;

	if (lp_winbind_cache_time() == 0) return;

	/* Error check */
	if (!sam_entries || (num_sam_entries == 0)) return;

	DEBUG(4, ("filling %s cache for domain %s with %d entries\n",
		  cache_type, domain_name, num_sam_entries));

	/* Store data as a mega-huge chunk in the tdb */
	slprintf(keystr, sizeof(keystr), "%s CACHE DATA/%s", cache_type,
		 domain_name);
	dos_to_unix(keystr, True);             /* Convert key to unix-codepage */
	tdb_store_by_string(cache_tdb, keystr, 
			    sam_entries, sizeof(struct acct_info) * num_sam_entries);

	/* Stamp cache with current seq number */
	set_cache_sequence_number(domain_name, cache_type, NULL);
}

/* Fill the user cache with supplied data */

void winbindd_fill_user_cache(char *domain_name, 
                              struct acct_info *sam_entries,
                              int num_sam_entries)
{
	fill_cache(domain_name, CACHE_TYPE_USER, sam_entries, num_sam_entries);
}

/* Fill the group cache with supplied data */

void winbindd_fill_group_cache(char *domain_name,
                               struct acct_info *sam_entries,
                               int num_sam_entries)
{
	fill_cache(domain_name, CACHE_TYPE_GROUP, sam_entries, num_sam_entries);
}

static void fill_cache_entry(char *domain, char *cache_type, char *name, void *buf, int len)
{
	fstring keystr;

	/* Create key for store */
	slprintf(keystr, sizeof(keystr), "%s/%s/%s", cache_type, domain, name);
	dos_to_unix(keystr, True);             /* Convert key to unix-codepage */

	DEBUG(4, ("filling cache entry %s\n", keystr));

	/* Store it */
	tdb_store_by_string(cache_tdb, keystr, buf, len);
}

/* Fill a user info cache entry */
void winbindd_fill_user_cache_entry(char *domain, char *user_name, 
                                    struct winbindd_pw *pw)
{
        if (lp_winbind_cache_time() == 0) return;

        fill_cache_entry(domain, CACHE_TYPE_USER, user_name, pw, sizeof(struct winbindd_pw));
	set_cache_sequence_number(domain, CACHE_TYPE_USER, user_name);
}

/* Fill a user uid cache entry */
void winbindd_fill_uid_cache_entry(char *domain, uid_t uid, 
                                    struct winbindd_pw *pw)
{
        fstring uidstr;

        if (lp_winbind_cache_time() == 0) return;

        slprintf(uidstr, sizeof(uidstr), "#%u", (unsigned)uid);
        fill_cache_entry(domain, CACHE_TYPE_USER, uidstr, pw, sizeof(struct winbindd_pw));
        set_cache_sequence_number(domain, CACHE_TYPE_USER, uidstr);
}

/* Fill a group info cache entry */
void winbindd_fill_group_cache_entry(char *domain, char *group_name, 
                                     struct winbindd_gr *gr, void *extra_data,
                                     int extra_data_len)
{
        fstring keystr;

        if (lp_winbind_cache_time() == 0) return;

        /* Fill group data */
        fill_cache_entry(domain, CACHE_TYPE_GROUP, group_name, gr, sizeof(struct winbindd_gr));

        /* Fill extra data */
        slprintf(keystr, sizeof(keystr), "%s/%s/%s DATA", CACHE_TYPE_GROUP, domain, group_name);
        dos_to_unix(keystr, True);         /* Convert key to unix-codepage */
        tdb_store_by_string(cache_tdb, keystr, extra_data, extra_data_len);

	set_cache_sequence_number(domain, CACHE_TYPE_GROUP, group_name);
}

/* Fill a group info cache entry */
void winbindd_fill_gid_cache_entry(char *domain, gid_t gid, 
                                     struct winbindd_gr *gr, void *extra_data,
                                     int extra_data_len)
{
        fstring keystr;
	fstring gidstr;

	slprintf(gidstr, sizeof(gidstr), "#%u", (unsigned)gid);

        if (lp_winbind_cache_time() == 0) return;

        /* Fill group data */
        fill_cache_entry(domain, CACHE_TYPE_GROUP, gidstr, gr, sizeof(struct winbindd_gr));

        /* Fill extra data */
        slprintf(keystr, sizeof(keystr), "%s/%s/%s DATA", CACHE_TYPE_GROUP, domain, gidstr);
        dos_to_unix(keystr, True);         /* Convert key to unix-codepage */
        tdb_store_by_string(cache_tdb, keystr, extra_data, extra_data_len);

	set_cache_sequence_number(domain, CACHE_TYPE_GROUP, gidstr);
}

/* Fetch some cached user or group data */
static BOOL fetch_cache(char *domain_name, char *cache_type,
                        struct acct_info **sam_entries, int *num_sam_entries)
{
        TDB_DATA data;
        fstring keystr;

        if (lp_winbind_cache_time() == 0) return False;

	/* Parameter check */
	if (!sam_entries || !num_sam_entries) {
		return False;
	}

	/* Check cache data is current */
	if (cache_domain_expired(domain_name, 
				 get_cache_sequence_number(domain_name, cache_type, NULL))) {
		return False;
	}
	
        /* Create key */        
        slprintf(keystr, sizeof(keystr), "%s CACHE DATA/%s", cache_type,
                 domain_name);
        dos_to_unix(keystr, True);         /* Convert key to unix-codepage */
	
        /* Fetch cache information */
	data = tdb_fetch_by_string(cache_tdb, keystr);
	
        if (!data.dptr) return False;

	/* Copy across cached data.  We can save a memcpy() by directly
	   assigning the data.dptr to the sam_entries pointer.  It will
	   be freed by the end{pw,gr}ent() function. */
	
	*sam_entries = (struct acct_info *)data.dptr;
	*num_sam_entries = data.dsize / sizeof(struct acct_info);
	
	DEBUG(4, ("fetched %d cached %s entries for domain %s\n",
		  *num_sam_entries, cache_type, domain_name));
	
	return True;
}

/* Return cached entries for a domain.  Return false if there are no cached
   entries, or the cached information has expired for the domain. */

BOOL winbindd_fetch_user_cache(char *domain_name,
                               struct acct_info **sam_entries,
                               int *num_entries)
{
	return fetch_cache(domain_name, CACHE_TYPE_USER, sam_entries,
			   num_entries);
}

/* Return cached entries for a domain.  Return false if there are no cached
   entries, or the cached information has expired for the domain. */

BOOL winbindd_fetch_group_cache(char *domain_name,
                                struct acct_info **sam_entries,
                                int *num_entries)
{
	return fetch_cache(domain_name, CACHE_TYPE_GROUP, sam_entries,
			   num_entries);
}

static BOOL fetch_cache_entry(char *domain, char *cache_type, char *name, void *buf, int len)
{
	TDB_DATA data;
	fstring keystr;
    
	/* Create key for lookup */
	slprintf(keystr, sizeof(keystr), "%s/%s/%s", cache_type, domain, name);
	dos_to_unix(keystr, True);             /* Convert key to unix-codepage */
    
	/* Look up cache entry */
	data = tdb_fetch_by_string(cache_tdb, keystr);
    	if (!data.dptr) return False;
        
        DEBUG(4, ("returning cached entry for %s\\%s\n", domain, name));

        /* Copy found entry into buffer */        
        memcpy((char *)buf, data.dptr, len < data.dsize ? len : data.dsize);
	free(data.dptr);
	return True;
}

/* Fetch an individual user cache entry */
BOOL winbindd_fetch_user_cache_entry(char *domain_name, char *user, 
                                     struct winbindd_pw *pw)
{
	uint32 seq_num;

	if (lp_winbind_cache_time() == 0) return False;

	seq_num = get_cache_sequence_number(domain_name, CACHE_TYPE_USER, user);
	if (cache_domain_expired(domain_name, seq_num)) return False;

	return fetch_cache_entry(domain_name, CACHE_TYPE_USER, user, pw, sizeof(struct winbindd_pw));
}

/* Fetch an individual uid cache entry */
BOOL winbindd_fetch_uid_cache_entry(char *domain_name, uid_t uid, 
				    struct winbindd_pw *pw)
{
	fstring uidstr;
	uint32 seq_num;

        if (lp_winbind_cache_time() == 0) return False;

	slprintf(uidstr, sizeof(uidstr), "#%u", (unsigned)uid);
    	seq_num = get_cache_sequence_number(domain_name, CACHE_TYPE_USER, uidstr);
	if (cache_domain_expired(domain_name, seq_num)) return False;

	return fetch_cache_entry(domain_name, CACHE_TYPE_USER, uidstr, pw, sizeof(struct winbindd_pw));
}

/* Fetch an individual group cache entry.  This function differs from the
   user cache code as we need to store the group membership data. */

BOOL winbindd_fetch_group_cache_entry(char *domain_name, char *group, 
                                      struct winbindd_gr *gr,
                                      void **extra_data, int *extra_data_len)
{
        TDB_DATA data;
        fstring keystr;
	uint32 seq_num;

        if (lp_winbind_cache_time() == 0) return False;

    	seq_num = get_cache_sequence_number(domain_name, CACHE_TYPE_GROUP, group);
	if (cache_domain_expired(domain_name, seq_num)) return False;

        /* Fetch group data */
        if (!fetch_cache_entry(domain_name, CACHE_TYPE_GROUP, group, gr, sizeof(struct winbindd_gr))) return False;
	
        /* Fetch extra data */
        slprintf(keystr, sizeof(keystr), "%s/%s/%s DATA", CACHE_TYPE_GROUP, domain_name, group);
        dos_to_unix(keystr, True);         /* Convert key to unix-codepage */
        data = tdb_fetch_by_string(cache_tdb, keystr);

        if (!data.dptr) return False;

	/* Extra data freed when data has been sent */
	if (extra_data) *extra_data = data.dptr;
	if (extra_data_len) *extra_data_len = data.dsize;
	
	return True;
}


/* Fetch an individual gid cache entry.  This function differs from the
   user cache code as we need to store the group membership data. */

BOOL winbindd_fetch_gid_cache_entry(char *domain_name, gid_t gid,
				    struct winbindd_gr *gr,
				    void **extra_data, int *extra_data_len)
{
        TDB_DATA data;
        fstring keystr;
	fstring gidstr;
	uint32 seq_num;

	slprintf(gidstr, sizeof(gidstr), "#%u", (unsigned)gid);
	
        if (lp_winbind_cache_time() == 0) return False;

    	seq_num = get_cache_sequence_number(domain_name, CACHE_TYPE_GROUP, gidstr);
	if (cache_domain_expired(domain_name, seq_num)) return False;

        /* Fetch group data */
        if (!fetch_cache_entry(domain_name, CACHE_TYPE_GROUP, 
			       gidstr, gr, sizeof(struct winbindd_gr))) return False;

        /* Fetch extra data */
        slprintf(keystr, sizeof(keystr), "%s/%s/%s DATA", CACHE_TYPE_GROUP, domain_name, gidstr);
        dos_to_unix(keystr, True);         /* Convert key to unix-codepage */
        data = tdb_fetch_by_string(cache_tdb, keystr);
        if (!data.dptr) return False;

	/* Extra data freed when data has been sent */
	if (extra_data) *extra_data = data.dptr;
	if (extra_data_len) *extra_data_len = data.dsize;

	return True;
}

/* Flush cache data - easiest to just reopen the tdb */
void winbindd_flush_cache(void)
{
	tdb_close(cache_tdb);
	winbindd_cache_init();
}