/*
 * Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved.
 * 
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */


//
// tokencache - persistent (on-disk) hardware token directory
//
// Here's the basic disk layout, rooted at /var/db/TokenCache (or $TOKENCACHE):
//  TBA
//
#include "tokencache.h"
#include <security_utilities/unix++.h>
#include <pwd.h>
#include <grp.h>

using namespace UnixPlusPlus;


//
// Here are the uid/gid values we assign to token daemons and their cache files
//
#define TOKEND_UID			"tokend"
#define TOKEND_GID			"tokend"
#define TOKEND_UID_FALLBACK	uid_t(-2)
#define TOKEND_GID_FALLBACK	gid_t(-2)


//
// Fixed relative file paths
//

// relative to cache root (use cache->path())
static const char configDir[] = "config";
static const char lastSSIDFile[] = "config/lastSSID";
static const char tokensDir[] = "tokens";

// relative to token directory (use token->path())
static const char ssidFile[] = "SSID";
static const char workDir[] = "work";
static const char cacheDir[] = "cache";


//
// Internal file I/O helpers. These read/write entire files.
// Note that the defaulted read functions do NOT write the default
// to disk; they work fine in read-only disk areas.
//
static uint32 getFile(const string &path, uint32 defaultValue)
{
	try {
		FileDesc fd(path);
		string s; fd.readAll(s);
		uint32 value; sscanf(s.c_str(), "%ld", &value);
		return value;
	} catch (...) {
		return defaultValue;
	}
}

static string getFile(const string &path, const string &defaultValue)
{
	try {
		FileDesc fd(path);
		string s; fd.readAll(s);
		return s;
	} catch (...) {
		return defaultValue;
	}
}


static void putFile(const string &path, uint32 value)
{
	char buffer[64];
	snprintf(buffer, sizeof(buffer), "%ld\n", value);
	FileDesc(path, O_WRONLY | O_CREAT | O_TRUNC).writeAll(buffer);
}

static void putFile(const string &path, const string &value)
{
	FileDesc(path, O_WRONLY | O_CREAT | O_TRUNC).writeAll(value);
}


//
// The "rooted tree" utility class
//
void Rooted::root(const string &r)
{
	assert(mRoot.empty());	// can't re-set this
	mRoot = r;
}

string Rooted::path(const char *sub) const
{
	if (sub == NULL)
		return mRoot;
	return mRoot + "/" + sub;
}


//
// Open a TokenCache.
// If the cache does not exist at the path given, initialize it.
// If that fails, throw an exception.
//
TokenCache::TokenCache(const char *where)
	: Rooted(where), mLastSubservice(0)
{
	makedir(root(), O_CREAT, 0711, securityd);
	makedir(path(configDir), O_CREAT, 0700, securityd);
	makedir(path(tokensDir), O_CREAT, 0711, securityd);
	
	// get the path for the SSID file.  Don't call getFile unless the file exists (avoids exception overhead)
	string idFilePath = path (lastSSIDFile);
	struct stat st;
	if (stat (idFilePath.c_str (), &st) == -1) {
		mLastSubservice = 1;
	} else {
		mLastSubservice = getFile(idFilePath, 1);
	}
	
	// identify uid/gid for token daemons
	struct passwd *pw = getpwnam(TOKEND_UID);
	mTokendUid = pw ? pw->pw_uid : TOKEND_UID_FALLBACK;
	struct group *gr = getgrnam(TOKEND_GID);
	mTokendGid = gr ? gr->gr_gid : TOKEND_GID_FALLBACK;
	
	secdebug("tokencache", "token cache rooted at %s (last ssid=%ld, uid/gid=%d/%d)",
		root().c_str(), mLastSubservice, mTokendUid, mTokendGid);
}

TokenCache::~TokenCache()
{
}


//
// Get a new, unused subservice id number.
// Update the tracking file so we won't hand it out again (ever) within this cache.
//
uint32 TokenCache::allocateSubservice()
{
	putFile(path(lastSSIDFile), ++mLastSubservice);
	return mLastSubservice;
}


//
// A slightly souped-up UnixPlusPlus::makedir
//
void TokenCache::makedir(const char *path, int flags, mode_t mode, Owner owner)
{
	UnixPlusPlus::makedir(path, flags, mode);
	switch(owner) {
	case securityd:
		// leave it alone; we own it alrady
		break;
	case tokend:
		::chown(path, tokendUid(), tokendGid());
		break;
	}
}


//
// Make a cache entry from a valid tokenUid.
// This will locate an existing entry or make a new one.
//
TokenCache::Token::Token(TokenCache &c, const string &tokenUid)
	: Rooted(c.path(string(tokensDir) + "/" + tokenUid)), cache(c)
{
	cache.makedir(root(), O_CREAT, 0711, securityd);
	if (mSubservice = getFile(path(ssidFile), 0)) {
		secdebug("tokencache", "found token \"%s\" ssid=%ld", tokenUid.c_str(), mSubservice);
		init(existing);
	} else {
		mSubservice = cache.allocateSubservice();   // allocate new, unique ssid...
		putFile(path(ssidFile), mSubservice);			// ... and save it in cache
		secdebug("tokencache", "new token \"%s\" ssid=%ld", tokenUid.c_str(), mSubservice);
		init(created);
	}
}


//
// Make a cache entry that is temporary and will never be reused.
//
TokenCache::Token::Token(TokenCache &c)
	: cache(c)
{
	mSubservice = cache.allocateSubservice();	// new, unique id
	char rootForm[30]; snprintf(rootForm, sizeof(rootForm),
		"%s/temporary:%ld", tokensDir, mSubservice);
	root(cache.path(rootForm));
	cache.makedir(root(), O_CREAT | O_EXCL, 0711, securityd);
	putFile(path(ssidFile), mSubservice);			// ... and save it in cache
	secdebug("tokencache", "temporary token \"%s\" ssid=%ld", rootForm, mSubservice);
	init(temporary);
}


//
// Common constructor setup code
//
void TokenCache::Token::init(Type type)
{
	mType = type;
	cache.makedir(workPath(), O_CREAT, 0700, tokend);
	cache.makedir(cachePath(), O_CREAT, 0700, tokend);
}


//
// The Token destructor might clean or preen a bit, but shouldn't take
// too long (or too much effort).
//
TokenCache::Token::~Token()
{
	if (type() == temporary)
		secdebug("tokencache", "@@@ should delete the cache directory here...");
}


//
// Attributes of TokenCache::Tokens
//
string TokenCache::Token::workPath() const
{
	return path("Work");
}

string TokenCache::Token::cachePath() const
{
	return path("Cache");
}


string TokenCache::Token::printName() const
{
	return getFile(path("PrintName"), "");
}

void TokenCache::Token::printName(const string &name)
{
	putFile(path("PrintName"), name);
}
