#include "k5-int.h"
#include "cc-int.h"
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#if HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#ifndef _WIN32
#include <dirent.h>
extern const krb5_cc_ops krb5_dcc_ops;
extern const krb5_cc_ops krb5_fcc_ops;
typedef struct dcc_data_st {
char *residual;
krb5_ccache fcc;
} dcc_data;
static inline krb5_boolean
filename_is_cache(const char *filename)
{
return (strncmp(filename, "tkt", 3) == 0);
}
static inline krb5_error_code
primary_pathname(const char *dirname, char **path_out)
{
return k5_path_join(dirname, "primary", path_out);
}
static krb5_error_code
subsidiary_residual(const char *dirname, const char *filename, char **out)
{
krb5_error_code ret;
char *path, *residual;
*out = NULL;
ret = k5_path_join(dirname, filename, &path);
if (ret)
return ret;
ret = asprintf(&residual, ":%s", path);
free(path);
if (ret < 0)
return ENOMEM;
*out = residual;
return 0;
}
static inline krb5_error_code
split_path(krb5_context context, const char *path, char **dirname_out,
char **filename_out)
{
krb5_error_code ret;
char *dirname, *filename;
*dirname_out = NULL;
*filename_out = NULL;
ret = k5_path_split(path, &dirname, &filename);
if (ret)
return ret;
if (*dirname == '\0') {
ret = KRB5_CC_BADNAME;
k5_setmsg(context, ret,
_("Subsidiary cache path %s has no parent directory"), path);
goto error;
}
if (!filename_is_cache(filename)) {
ret = KRB5_CC_BADNAME;
k5_setmsg(context, ret,
_("Subsidiary cache path %s filename does not begin with "
"\"tkt\""), path);
goto error;
}
*dirname_out = dirname;
*filename_out = filename;
return 0;
error:
free(dirname);
free(filename);
return ret;
}
static krb5_error_code
read_primary_file(krb5_context context, const char *primary_path,
const char *dirname, char **residual_out)
{
FILE *fp;
char buf[64], *ret;
size_t len;
*residual_out = NULL;
fp = fopen(primary_path, "r");
if (fp == NULL)
return ENOENT;
ret = fgets(buf, sizeof(buf), fp);
fclose(fp);
if (ret == NULL)
return KRB5_CC_IO;
len = strlen(buf);
if (buf[len - 1] != '\n' || !filename_is_cache(buf) ||
strchr(buf, '/') || strchr(buf, '\\')) {
k5_setmsg(context, KRB5_CC_FORMAT, _("%s contains invalid filename"),
primary_path);
return KRB5_CC_FORMAT;
}
buf[len - 1] = '\0';
return subsidiary_residual(dirname, buf, residual_out);
}
static krb5_error_code
write_primary_file(const char *primary_path, const char *contents)
{
krb5_error_code ret = KRB5_CC_IO;
char *newpath = NULL;
FILE *fp = NULL;
int fd = -1, status;
if (asprintf(&newpath, "%s.XXXXXX", primary_path) < 0)
return ENOMEM;
fd = mkstemp(newpath);
if (fd < 0)
goto cleanup;
#ifdef HAVE_CHMOD
chmod(newpath, S_IRUSR | S_IWUSR);
#endif
fp = fdopen(fd, "w");
if (fp == NULL)
goto cleanup;
fd = -1;
if (fprintf(fp, "%s\n", contents) < 0)
goto cleanup;
status = fclose(fp);
fp = NULL;
if (status == EOF)
goto cleanup;
fp = NULL;
if (rename(newpath, primary_path) != 0)
goto cleanup;
ret = 0;
cleanup:
if (fd >= 0)
close(fd);
if (fp != NULL)
fclose(fp);
free(newpath);
return ret;
}
static krb5_error_code
verify_dir(krb5_context context, const char *dirname)
{
struct stat st;
if (stat(dirname, &st) < 0) {
if (errno == ENOENT && mkdir(dirname, S_IRWXU) == 0)
return 0;
k5_setmsg(context, KRB5_FCC_NOFILE,
_("Credential cache directory %s does not exist"),
dirname);
return KRB5_FCC_NOFILE;
}
if (!S_ISDIR(st.st_mode)) {
k5_setmsg(context, KRB5_CC_FORMAT,
_("Credential cache directory %s exists but is not a "
"directory"), dirname);
return KRB5_CC_FORMAT;
}
return 0;
}
static krb5_error_code
get_context_default_dir(krb5_context context, char **dirname_out)
{
const char *defname;
char *dirname;
*dirname_out = NULL;
defname = krb5_cc_default_name(context);
if (defname == NULL)
return 0;
if (strncmp(defname, "DIR:", 4) != 0 ||
defname[4] == ':' || defname[4] == '\0')
return 0;
dirname = strdup(defname + 4);
if (dirname == NULL)
return ENOMEM;
*dirname_out = dirname;
return 0;
}
static krb5_error_code
get_context_subsidiary_file(krb5_context context, char **subsidiary_out)
{
const char *defname;
char *residual;
*subsidiary_out = NULL;
defname = krb5_cc_default_name(context);
if (defname == NULL || strncmp(defname, "DIR::", 5) != 0)
return 0;
residual = strdup(defname + 4);
if (residual == NULL)
return ENOMEM;
*subsidiary_out = residual;
return 0;
}
static const char * KRB5_CALLCONV
dcc_get_name(krb5_context context, krb5_ccache cache)
{
dcc_data *data = cache->data;
return data->residual;
}
static krb5_error_code
make_cache(const char *residual, krb5_ccache fcc, krb5_ccache *cache_out)
{
krb5_ccache cache = NULL;
dcc_data *data = NULL;
char *residual_copy = NULL;
cache = malloc(sizeof(*cache));
if (cache == NULL)
goto oom;
data = malloc(sizeof(*data));
if (data == NULL)
goto oom;
residual_copy = strdup(residual);
if (residual_copy == NULL)
goto oom;
data->residual = residual_copy;
data->fcc = fcc;
cache->ops = &krb5_dcc_ops;
cache->data = data;
cache->magic = KV5M_CCACHE;
*cache_out = cache;
return 0;
oom:
free(cache);
free(data);
free(residual_copy);
return ENOMEM;
}
static krb5_error_code KRB5_CALLCONV
dcc_resolve(krb5_context context, krb5_ccache *cache_out, const char *residual)
{
krb5_error_code ret;
krb5_ccache fcc;
char *primary_path = NULL, *sresidual = NULL, *dirname, *filename;
*cache_out = NULL;
if (*residual == ':') {
ret = split_path(context, residual + 1, &dirname, &filename);
if (ret)
return ret;
ret = verify_dir(context, dirname);
free(dirname);
free(filename);
if (ret)
return ret;
} else {
ret = verify_dir(context, residual);
if (ret)
return ret;
ret = primary_pathname(residual, &primary_path);
if (ret)
goto cleanup;
ret = read_primary_file(context, primary_path, residual, &sresidual);
if (ret == ENOENT) {
ret = write_primary_file(primary_path, "tkt");
if (ret)
goto cleanup;
ret = subsidiary_residual(residual, "tkt", &sresidual);
}
if (ret)
goto cleanup;
residual = sresidual;
}
ret = krb5_fcc_ops.resolve(context, &fcc, residual + 1);
if (ret)
goto cleanup;
ret = make_cache(residual, fcc, cache_out);
if (ret)
krb5_fcc_ops.close(context, fcc);
cleanup:
free(primary_path);
free(sresidual);
return ret;
}
static krb5_error_code KRB5_CALLCONV
dcc_gen_new(krb5_context context, krb5_ccache *cache_out)
{
krb5_error_code ret;
char *dirname = NULL, *template = NULL, *residual = NULL;
krb5_ccache fcc = NULL;
*cache_out = NULL;
ret = get_context_default_dir(context, &dirname);
if (ret)
return ret;
if (dirname == NULL) {
k5_setmsg(context, KRB5_DCC_CANNOT_CREATE,
_("Can't create new subsidiary cache because default cache "
"is not a directory collection"));
return KRB5_DCC_CANNOT_CREATE;
}
ret = verify_dir(context, dirname);
if (ret)
goto cleanup;
ret = k5_path_join(dirname, "tktXXXXXX", &template);
if (ret)
goto cleanup;
ret = krb5int_fcc_new_unique(context, template, &fcc);
if (ret)
goto cleanup;
if (asprintf(&residual, ":%s", template) < 0) {
ret = ENOMEM;
goto cleanup;
}
ret = make_cache(residual, fcc, cache_out);
if (ret)
goto cleanup;
fcc = NULL;
cleanup:
if (fcc != NULL)
krb5_fcc_ops.destroy(context, fcc);
free(dirname);
free(template);
free(residual);
return ret;
}
static krb5_error_code KRB5_CALLCONV
dcc_init(krb5_context context, krb5_ccache cache, krb5_principal princ)
{
dcc_data *data = cache->data;
return krb5_fcc_ops.init(context, data->fcc, princ);
}
static krb5_error_code KRB5_CALLCONV
dcc_destroy(krb5_context context, krb5_ccache cache)
{
dcc_data *data = cache->data;
krb5_error_code ret;
ret = krb5_fcc_ops.destroy(context, data->fcc);
free(data->residual);
free(data);
free(cache);
return ret;
}
static krb5_error_code KRB5_CALLCONV
dcc_close(krb5_context context, krb5_ccache cache)
{
dcc_data *data = cache->data;
krb5_error_code ret;
ret = krb5_fcc_ops.close(context, data->fcc);
free(data->residual);
free(data);
free(cache);
return ret;
}
static krb5_error_code KRB5_CALLCONV
dcc_store(krb5_context context, krb5_ccache cache, krb5_creds *creds)
{
dcc_data *data = cache->data;
return krb5_fcc_ops.store(context, data->fcc, creds);
}
static krb5_error_code KRB5_CALLCONV
dcc_retrieve(krb5_context context, krb5_ccache cache, krb5_flags flags,
krb5_creds *mcreds, krb5_creds *creds)
{
dcc_data *data = cache->data;
return krb5_fcc_ops.retrieve(context, data->fcc, flags, mcreds,
creds);
}
static krb5_error_code KRB5_CALLCONV
dcc_get_princ(krb5_context context, krb5_ccache cache,
krb5_principal *princ_out)
{
dcc_data *data = cache->data;
return krb5_fcc_ops.get_princ(context, data->fcc, princ_out);
}
static krb5_error_code KRB5_CALLCONV
dcc_get_first(krb5_context context, krb5_ccache cache, krb5_cc_cursor *cursor)
{
dcc_data *data = cache->data;
return krb5_fcc_ops.get_first(context, data->fcc, cursor);
}
static krb5_error_code KRB5_CALLCONV
dcc_get_next(krb5_context context, krb5_ccache cache, krb5_cc_cursor *cursor,
krb5_creds *creds)
{
dcc_data *data = cache->data;
return krb5_fcc_ops.get_next(context, data->fcc, cursor, creds);
}
static krb5_error_code KRB5_CALLCONV
dcc_end_get(krb5_context context, krb5_ccache cache, krb5_cc_cursor *cursor)
{
dcc_data *data = cache->data;
return krb5_fcc_ops.end_get(context, data->fcc, cursor);
}
static krb5_error_code KRB5_CALLCONV
dcc_remove_cred(krb5_context context, krb5_ccache cache, krb5_flags flags,
krb5_creds *creds)
{
dcc_data *data = cache->data;
return krb5_fcc_ops.remove_cred(context, data->fcc, flags, creds);
}
static krb5_error_code KRB5_CALLCONV
dcc_set_flags(krb5_context context, krb5_ccache cache, krb5_flags flags)
{
dcc_data *data = cache->data;
return krb5_fcc_ops.set_flags(context, data->fcc, flags);
}
static krb5_error_code KRB5_CALLCONV
dcc_get_flags(krb5_context context, krb5_ccache cache, krb5_flags *flags_out)
{
dcc_data *data = cache->data;
return krb5_fcc_ops.get_flags(context, data->fcc, flags_out);
}
struct dcc_ptcursor_data {
char *primary;
char *dirname;
DIR *dir;
krb5_boolean first;
};
static krb5_error_code
make_cursor(char *dirname, char *primary, DIR *dir,
krb5_cc_ptcursor *cursor_out)
{
krb5_cc_ptcursor cursor;
struct dcc_ptcursor_data *data;
*cursor_out = NULL;
data = malloc(sizeof(*data));
if (data == NULL)
return ENOMEM;
cursor = malloc(sizeof(*cursor));
if (cursor == NULL) {
free(data);
return ENOMEM;
}
data->dirname = dirname;
data->primary = primary;
data->dir = dir;
data->first = TRUE;
cursor->ops = &krb5_dcc_ops;
cursor->data = data;
*cursor_out = cursor;
return 0;
}
static krb5_error_code KRB5_CALLCONV
dcc_ptcursor_new(krb5_context context, krb5_cc_ptcursor *cursor_out)
{
krb5_error_code ret;
char *dirname = NULL, *primary_path = NULL, *primary = NULL;
DIR *dir = NULL;
*cursor_out = NULL;
ret = get_context_subsidiary_file(context, &primary);
if (ret)
goto cleanup;
if (primary != NULL) {
ret = make_cursor(NULL, primary, NULL, cursor_out);
if (ret)
free(primary);
return ret;
}
ret = get_context_default_dir(context, &dirname);
if (ret || dirname == NULL)
goto cleanup;
dir = opendir(dirname);
if (dir == NULL)
goto cleanup;
ret = primary_pathname(dirname, &primary_path);
if (ret)
goto cleanup;
ret = read_primary_file(context, primary_path, dirname, &primary);
if (ret)
krb5_clear_error_message(context);
ret = make_cursor(dirname, primary, dir, cursor_out);
if (ret)
goto cleanup;
dirname = primary = NULL;
dir = NULL;
cleanup:
free(dirname);
free(primary_path);
free(primary);
if (dir)
closedir(dir);
if (*cursor_out == NULL)
return make_cursor(NULL, NULL, NULL, cursor_out);
return 0;
}
static krb5_error_code KRB5_CALLCONV
dcc_ptcursor_next(krb5_context context, krb5_cc_ptcursor cursor,
krb5_ccache *cache_out)
{
struct dcc_ptcursor_data *data = cursor->data;
struct dirent *ent;
char *residual;
krb5_error_code ret;
struct stat sb;
*cache_out = NULL;
if (data->first) {
data->first = FALSE;
if (data->primary != NULL && stat(data->primary + 1, &sb) == 0)
return dcc_resolve(context, cache_out, data->primary);
}
if (data->dir == NULL)
return 0;
while ((ent = readdir(data->dir)) != NULL) {
if (!filename_is_cache(ent->d_name))
continue;
ret = subsidiary_residual(data->dirname, ent->d_name, &residual);
if (ret)
return ret;
if (data->primary != NULL && strcmp(residual, data->primary) == 0) {
free(residual);
continue;
}
ret = dcc_resolve(context, cache_out, residual);
free(residual);
return ret;
}
closedir(data->dir);
data->dir = NULL;
return 0;
}
static krb5_error_code KRB5_CALLCONV
dcc_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor)
{
struct dcc_ptcursor_data *data = (*cursor)->data;
if (data->dir)
closedir(data->dir);
free(data->dirname);
free(data->primary);
free(data);
free(*cursor);
*cursor = NULL;
return 0;
}
static krb5_error_code KRB5_CALLCONV
dcc_replace(krb5_context context, krb5_ccache cache, krb5_principal princ,
krb5_creds **creds)
{
dcc_data *data = cache->data;
return krb5_fcc_ops.replace(context, data->fcc, princ, creds);
}
static krb5_error_code KRB5_CALLCONV
dcc_lock(krb5_context context, krb5_ccache cache)
{
dcc_data *data = cache->data;
return krb5_fcc_ops.lock(context, data->fcc);
}
static krb5_error_code KRB5_CALLCONV
dcc_unlock(krb5_context context, krb5_ccache cache)
{
dcc_data *data = cache->data;
return krb5_fcc_ops.unlock(context, data->fcc);
}
static krb5_error_code KRB5_CALLCONV
dcc_switch_to(krb5_context context, krb5_ccache cache)
{
dcc_data *data = cache->data;
char *primary_path = NULL, *dirname = NULL, *filename = NULL;
krb5_error_code ret;
ret = split_path(context, data->residual + 1, &dirname, &filename);
if (ret)
return ret;
ret = primary_pathname(dirname, &primary_path);
if (ret)
goto cleanup;
ret = write_primary_file(primary_path, filename);
cleanup:
free(primary_path);
free(dirname);
free(filename);
return ret;
}
const krb5_cc_ops krb5_dcc_ops = {
0,
"DIR",
dcc_get_name,
dcc_resolve,
dcc_gen_new,
dcc_init,
dcc_destroy,
dcc_close,
dcc_store,
dcc_retrieve,
dcc_get_princ,
dcc_get_first,
dcc_get_next,
dcc_end_get,
dcc_remove_cred,
dcc_set_flags,
dcc_get_flags,
dcc_ptcursor_new,
dcc_ptcursor_next,
dcc_ptcursor_free,
dcc_replace,
NULL,
dcc_lock,
dcc_unlock,
dcc_switch_to,
};
#endif