freebsd-nq/eBones/krb/tf_util.c
1995-05-30 06:41:30 +00:00

573 lines
15 KiB
C

/*
* Copyright 1987, 1988 by the Massachusetts Institute of Technology.
* For copying and distribution information, please see the file
* <Copyright.MIT>.
*
* from: tf_util.c,v 4.9 90/03/10 19:19:45 jon Exp $
* $Id: tf_util.c,v 1.1.1.1 1994/09/30 14:50:04 csgr Exp $
*/
#ifndef lint
static char rcsid[] =
"$Id: tf_util.c,v 1.1.1.1 1994/09/30 14:50:04 csgr Exp $";
#endif /* lint */
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <krb.h>
#ifdef TKT_SHMEM
#include <sys/param.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#endif /* TKT_SHMEM */
#define TOO_BIG -1
#define TF_LCK_RETRY ((unsigned)2) /* seconds to sleep before
* retry if ticket file is
* locked */
extern errno;
extern int krb_debug;
#ifdef TKT_SHMEM
char *krb_shm_addr = 0;
static char *tmp_shm_addr = 0;
static char krb_dummy_skey[8] = {0,0,0,0,0,0,0,0};
char *shmat();
#endif /* TKT_SHMEM */
/*
* fd must be initialized to something that won't ever occur as a real
* file descriptor. Since open(2) returns only non-negative numbers as
* valid file descriptors, and tf_init always stuffs the return value
* from open in here even if it is an error flag, we must
* a. Initialize fd to a negative number, to indicate that it is
* not initially valid.
* b. When checking for a valid fd, assume that negative values
* are invalid (ie. when deciding whether tf_init has been
* called.)
* c. In tf_close, be sure it gets reinitialized to a negative
* number.
*/
static fd = -1;
static curpos; /* Position in tfbfr */
static lastpos; /* End of tfbfr */
static char tfbfr[BUFSIZ]; /* Buffer for ticket data */
static tf_gets(), tf_read();
/*
* This file contains routines for manipulating the ticket cache file.
*
* The ticket file is in the following format:
*
* principal's name (null-terminated string)
* principal's instance (null-terminated string)
* CREDENTIAL_1
* CREDENTIAL_2
* ...
* CREDENTIAL_n
* EOF
*
* Where "CREDENTIAL_x" consists of the following fixed-length
* fields from the CREDENTIALS structure (see "krb.h"):
*
* char service[ANAME_SZ]
* char instance[INST_SZ]
* char realm[REALM_SZ]
* C_Block session
* int lifetime
* int kvno
* KTEXT_ST ticket_st
* long issue_date
*
* Short description of routines:
*
* tf_init() opens the ticket file and locks it.
*
* tf_get_pname() returns the principal's name.
*
* tf_get_pinst() returns the principal's instance (may be null).
*
* tf_get_cred() returns the next CREDENTIALS record.
*
* tf_save_cred() appends a new CREDENTIAL record to the ticket file.
*
* tf_close() closes the ticket file and releases the lock.
*
* tf_gets() returns the next null-terminated string. It's an internal
* routine used by tf_get_pname(), tf_get_pinst(), and tf_get_cred().
*
* tf_read() reads a given number of bytes. It's an internal routine
* used by tf_get_cred().
*/
/*
* tf_init() should be called before the other ticket file routines.
* It takes the name of the ticket file to use, "tf_name", and a
* read/write flag "rw" as arguments.
*
* It tries to open the ticket file, checks the mode, and if everything
* is okay, locks the file. If it's opened for reading, the lock is
* shared. If it's opened for writing, the lock is exclusive.
*
* Returns KSUCCESS if all went well, otherwise one of the following:
*
* NO_TKT_FIL - file wasn't there
* TKT_FIL_ACC - file was in wrong mode, etc.
* TKT_FIL_LCK - couldn't lock the file, even after a retry
*/
tf_init(tf_name, rw)
char *tf_name;
{
int wflag;
uid_t me, getuid();
struct stat stat_buf;
#ifdef TKT_SHMEM
char shmidname[MAXPATHLEN];
FILE *sfp;
int shmid;
#endif
switch (rw) {
case R_TKT_FIL:
wflag = 0;
break;
case W_TKT_FIL:
wflag = 1;
break;
default:
if (krb_debug) fprintf(stderr, "tf_init: illegal parameter\n");
return TKT_FIL_ACC;
}
if (lstat(tf_name, &stat_buf) < 0)
switch (errno) {
case ENOENT:
return NO_TKT_FIL;
default:
return TKT_FIL_ACC;
}
me = getuid();
if ((stat_buf.st_uid != me && me != 0) ||
((stat_buf.st_mode & S_IFMT) != S_IFREG))
return TKT_FIL_ACC;
#ifdef TKT_SHMEM
(void) strcpy(shmidname, tf_name);
(void) strcat(shmidname, ".shm");
if (stat(shmidname,&stat_buf) < 0)
return(TKT_FIL_ACC);
if ((stat_buf.st_uid != me && me != 0) ||
((stat_buf.st_mode & S_IFMT) != S_IFREG))
return TKT_FIL_ACC;
#endif /* TKT_SHMEM */
/*
* If "wflag" is set, open the ticket file in append-writeonly mode
* and lock the ticket file in exclusive mode. If unable to lock
* the file, sleep and try again. If we fail again, return with the
* proper error message.
*/
curpos = sizeof(tfbfr);
#ifdef TKT_SHMEM
sfp = fopen(shmidname, "r"); /* only need read/write on the
actual tickets */
if (sfp == 0)
return TKT_FIL_ACC;
shmid = -1;
{
char buf[BUFSIZ];
int val; /* useful for debugging fscanf */
/* We provide our own buffer here since some STDIO libraries
barf on unbuffered input with fscanf() */
setbuf(sfp, buf);
if ((val = fscanf(sfp,"%d",&shmid)) != 1) {
(void) fclose(sfp);
return TKT_FIL_ACC;
}
if (shmid < 0) {
(void) fclose(sfp);
return TKT_FIL_ACC;
}
(void) fclose(sfp);
}
/*
* global krb_shm_addr is initialized to 0. Ultrix bombs when you try and
* attach the same segment twice so we need this check.
*/
if (!krb_shm_addr) {
if ((krb_shm_addr = shmat(shmid,0,0)) == -1){
if (krb_debug)
fprintf(stderr,
"cannot attach shared memory for segment %d\n",
shmid);
krb_shm_addr = 0; /* reset so we catch further errors */
return TKT_FIL_ACC;
}
}
tmp_shm_addr = krb_shm_addr;
#endif /* TKT_SHMEM */
if (wflag) {
fd = open(tf_name, O_RDWR, 0600);
if (fd < 0) {
return TKT_FIL_ACC;
}
if (flock(fd, LOCK_EX | LOCK_NB) < 0) {
sleep(TF_LCK_RETRY);
if (flock(fd, LOCK_EX | LOCK_NB) < 0) {
(void) close(fd);
fd = -1;
return TKT_FIL_LCK;
}
}
return KSUCCESS;
}
/*
* Otherwise "wflag" is not set and the ticket file should be opened
* for read-only operations and locked for shared access.
*/
fd = open(tf_name, O_RDONLY, 0600);
if (fd < 0) {
return TKT_FIL_ACC;
}
if (flock(fd, LOCK_SH | LOCK_NB) < 0) {
sleep(TF_LCK_RETRY);
if (flock(fd, LOCK_SH | LOCK_NB) < 0) {
(void) close(fd);
fd = -1;
return TKT_FIL_LCK;
}
}
return KSUCCESS;
}
/*
* tf_get_pname() reads the principal's name from the ticket file. It
* should only be called after tf_init() has been called. The
* principal's name is filled into the "p" parameter. If all goes well,
* KSUCCESS is returned. If tf_init() wasn't called, TKT_FIL_INI is
* returned. If the name was null, or EOF was encountered, or the name
* was longer than ANAME_SZ, TKT_FIL_FMT is returned.
*/
tf_get_pname(p)
char *p;
{
if (fd < 0) {
if (krb_debug)
fprintf(stderr, "tf_get_pname called before tf_init.\n");
return TKT_FIL_INI;
}
if (tf_gets(p, ANAME_SZ) < 2) /* can't be just a null */
return TKT_FIL_FMT;
return KSUCCESS;
}
/*
* tf_get_pinst() reads the principal's instance from a ticket file.
* It should only be called after tf_init() and tf_get_pname() have been
* called. The instance is filled into the "inst" parameter. If all
* goes well, KSUCCESS is returned. If tf_init() wasn't called,
* TKT_FIL_INI is returned. If EOF was encountered, or the instance
* was longer than ANAME_SZ, TKT_FIL_FMT is returned. Note that the
* instance may be null.
*/
tf_get_pinst(inst)
char *inst;
{
if (fd < 0) {
if (krb_debug)
fprintf(stderr, "tf_get_pinst called before tf_init.\n");
return TKT_FIL_INI;
}
if (tf_gets(inst, INST_SZ) < 1)
return TKT_FIL_FMT;
return KSUCCESS;
}
/*
* tf_get_cred() reads a CREDENTIALS record from a ticket file and fills
* in the given structure "c". It should only be called after tf_init(),
* tf_get_pname(), and tf_get_pinst() have been called. If all goes well,
* KSUCCESS is returned. Possible error codes are:
*
* TKT_FIL_INI - tf_init wasn't called first
* TKT_FIL_FMT - bad format
* EOF - end of file encountered
*/
tf_get_cred(c)
CREDENTIALS *c;
{
KTEXT ticket = &c->ticket_st; /* pointer to ticket */
int k_errno;
if (fd < 0) {
if (krb_debug)
fprintf(stderr, "tf_get_cred called before tf_init.\n");
return TKT_FIL_INI;
}
if ((k_errno = tf_gets(c->service, SNAME_SZ)) < 2)
switch (k_errno) {
case TOO_BIG:
case 1: /* can't be just a null */
tf_close();
return TKT_FIL_FMT;
case 0:
return EOF;
}
if ((k_errno = tf_gets(c->instance, INST_SZ)) < 1)
switch (k_errno) {
case TOO_BIG:
return TKT_FIL_FMT;
case 0:
return EOF;
}
if ((k_errno = tf_gets(c->realm, REALM_SZ)) < 2)
switch (k_errno) {
case TOO_BIG:
case 1: /* can't be just a null */
tf_close();
return TKT_FIL_FMT;
case 0:
return EOF;
}
if (
tf_read((char *) (c->session), KEY_SZ) < 1 ||
tf_read((char *) &(c->lifetime), sizeof(c->lifetime)) < 1 ||
tf_read((char *) &(c->kvno), sizeof(c->kvno)) < 1 ||
tf_read((char *) &(ticket->length), sizeof(ticket->length))
< 1 ||
/* don't try to read a silly amount into ticket->dat */
ticket->length > MAX_KTXT_LEN ||
tf_read((char *) (ticket->dat), ticket->length) < 1 ||
tf_read((char *) &(c->issue_date), sizeof(c->issue_date)) < 1
) {
tf_close();
return TKT_FIL_FMT;
}
#ifdef TKT_SHMEM
bcopy(tmp_shm_addr,c->session,KEY_SZ);
tmp_shm_addr += KEY_SZ;
#endif /* TKT_SHMEM */
return KSUCCESS;
}
/*
* tf_close() closes the ticket file and sets "fd" to -1. If "fd" is
* not a valid file descriptor, it just returns. It also clears the
* buffer used to read tickets.
*
* The return value is not defined.
*/
tf_close()
{
if (!(fd < 0)) {
#ifdef TKT_SHMEM
if (shmdt(krb_shm_addr)) {
/* what kind of error? */
if (krb_debug)
fprintf(stderr, "shmdt 0x%x: errno %d",krb_shm_addr, errno);
} else {
krb_shm_addr = 0;
}
#endif TKT_SHMEM
(void) flock(fd, LOCK_UN);
(void) close(fd);
fd = -1; /* see declaration of fd above */
}
bzero(tfbfr, sizeof(tfbfr));
}
/*
* tf_gets() is an internal routine. It takes a string "s" and a count
* "n", and reads from the file until either it has read "n" characters,
* or until it reads a null byte. When finished, what has been read exists
* in "s". If it encounters EOF or an error, it closes the ticket file.
*
* Possible return values are:
*
* n the number of bytes read (including null terminator)
* when all goes well
*
* 0 end of file or read error
*
* TOO_BIG if "count" characters are read and no null is
* encountered. This is an indication that the ticket
* file is seriously ill.
*/
static
tf_gets(s, n)
register char *s;
{
register count;
if (fd < 0) {
if (krb_debug)
fprintf(stderr, "tf_gets called before tf_init.\n");
return TKT_FIL_INI;
}
for (count = n - 1; count > 0; --count) {
if (curpos >= sizeof(tfbfr)) {
lastpos = read(fd, tfbfr, sizeof(tfbfr));
curpos = 0;
}
if (curpos == lastpos) {
tf_close();
return 0;
}
*s = tfbfr[curpos++];
if (*s++ == '\0')
return (n - count);
}
tf_close();
return TOO_BIG;
}
/*
* tf_read() is an internal routine. It takes a string "s" and a count
* "n", and reads from the file until "n" bytes have been read. When
* finished, what has been read exists in "s". If it encounters EOF or
* an error, it closes the ticket file.
*
* Possible return values are:
*
* n the number of bytes read when all goes well
*
* 0 on end of file or read error
*/
static
tf_read(s, n)
register char *s;
register n;
{
register count;
for (count = n; count > 0; --count) {
if (curpos >= sizeof(tfbfr)) {
lastpos = read(fd, tfbfr, sizeof(tfbfr));
curpos = 0;
}
if (curpos == lastpos) {
tf_close();
return 0;
}
*s++ = tfbfr[curpos++];
}
return n;
}
char *tkt_string();
/*
* tf_save_cred() appends an incoming ticket to the end of the ticket
* file. You must call tf_init() before calling tf_save_cred().
*
* The "service", "instance", and "realm" arguments specify the
* server's name; "session" contains the session key to be used with
* the ticket; "kvno" is the server key version number in which the
* ticket is encrypted, "ticket" contains the actual ticket, and
* "issue_date" is the time the ticket was requested (local host's time).
*
* Returns KSUCCESS if all goes well, TKT_FIL_INI if tf_init() wasn't
* called previously, and KFAILURE for anything else that went wrong.
*/
tf_save_cred(service, instance, realm, session, lifetime, kvno,
ticket, issue_date)
char *service; /* Service name */
char *instance; /* Instance */
char *realm; /* Auth domain */
C_Block session; /* Session key */
int lifetime; /* Lifetime */
int kvno; /* Key version number */
KTEXT ticket; /* The ticket itself */
long issue_date; /* The issue time */
{
off_t lseek();
int count; /* count for write */
#ifdef TKT_SHMEM
int *skey_check;
#endif /* TKT_SHMEM */
if (fd < 0) { /* fd is ticket file as set by tf_init */
if (krb_debug)
fprintf(stderr, "tf_save_cred called before tf_init.\n");
return TKT_FIL_INI;
}
/* Find the end of the ticket file */
(void) lseek(fd, 0L, 2);
#ifdef TKT_SHMEM
/* scan to end of existing keys: pick first 'empty' slot.
we assume that no real keys will be completely zero (it's a weak
key under DES) */
skey_check = (int *) krb_shm_addr;
while (*skey_check && *(skey_check+1))
skey_check += 2;
tmp_shm_addr = (char *)skey_check;
#endif /* TKT_SHMEM */
/* Write the ticket and associated data */
/* Service */
count = strlen(service) + 1;
if (write(fd, service, count) != count)
goto bad;
/* Instance */
count = strlen(instance) + 1;
if (write(fd, instance, count) != count)
goto bad;
/* Realm */
count = strlen(realm) + 1;
if (write(fd, realm, count) != count)
goto bad;
/* Session key */
#ifdef TKT_SHMEM
bcopy(session,tmp_shm_addr,8);
tmp_shm_addr+=8;
if (write(fd,krb_dummy_skey,8) != 8)
goto bad;
#else /* ! TKT_SHMEM */
if (write(fd, (char *) session, 8) != 8)
goto bad;
#endif /* TKT_SHMEM */
/* Lifetime */
if (write(fd, (char *) &lifetime, sizeof(int)) != sizeof(int))
goto bad;
/* Key vno */
if (write(fd, (char *) &kvno, sizeof(int)) != sizeof(int))
goto bad;
/* Tkt length */
if (write(fd, (char *) &(ticket->length), sizeof(int)) !=
sizeof(int))
goto bad;
/* Ticket */
count = ticket->length;
if (write(fd, (char *) (ticket->dat), count) != count)
goto bad;
/* Issue date */
if (write(fd, (char *) &issue_date, sizeof(long))
!= sizeof(long))
goto bad;
/* Actually, we should check each write for success */
return (KSUCCESS);
bad:
return (KFAILURE);
}