/* * Copyright 2000, International Business Machines Corporation and others. * All Rights Reserved. * * This software has been released under the terms of the IBM Public * License. For details, see the LICENSE file in the top-level source * directory or online at http://www.openafs.org/dl/license10.html */ #include #include #ifdef AFS_NT40_ENV #include #else #include #endif #include #include #include #include #include #include "kauth.h" #include "kautils.h" #include "kaserver.h" extern struct kaheader cheader; extern Date cheaderReadTime; /* time cheader last read in */ extern struct ubik_dbase *KA_dbase; #define set_header_word(tt,field,value) kawrite ((tt), ((char *)&(cheader.field) - (char *)&cheader), ((cheader.field = (value)), (char *)&(cheader.field)), sizeof(afs_int32)) #define inc_header_word(tt,field) kawrite ((tt), ((char *)&(cheader.field) - (char *)&cheader), ((cheader.field = (htonl(ntohl(cheader.field)+1))), (char *)&(cheader.field)), sizeof(afs_int32)) static int index_OK(); afs_int32 NameHash (aname, ainstance) register char *aname; register char *ainstance; { register unsigned int hash; register int i; /* stolen directly from the HashString function in the vol package */ hash = 0; for (i=strlen(aname), aname += i-1; i--; aname--) hash = (hash*31) + (*((unsigned char *)aname) - 31); for (i=strlen(ainstance), ainstance += i-1; i--; ainstance--) hash = (hash*31) + (*((unsigned char *)ainstance) - 31); return(hash % HASHSIZE); } /* package up seek and write into one procedure for ease of use */ afs_int32 kawrite (tt, pos, buff, len) struct ubik_trans *tt; afs_int32 pos; char *buff; afs_int32 len; { afs_int32 code; code = ubik_Seek(tt, 0, pos); if (code) return code; code = ubik_Write(tt,buff,len); return code; } /* same thing for read */ afs_int32 karead (tt, pos, buff, len) struct ubik_trans *tt; afs_int32 pos; char *buff; afs_int32 len; { afs_int32 code; code = ubik_Seek(tt, 0, pos); if (code) return code; code = ubik_Read(tt, buff, len); return code; } static struct Lock cheader_lock; static struct Lock keycache_lock; static int maxCachedKeys; static struct cachedKey { Date used; int superseded; /* NEVERDATE => this is current key */ afs_int32 kvno; struct ktc_encryptionKey key; char name[MAXKTCNAMELEN]; char inst[MAXKTCNAMELEN]; } *keyCache; static afs_int32 keyCacheVersion = 0; static afs_int32 maxKeyLifetime; static int dbfixup = 0; init_kadatabase (initFlags) int initFlags; /* same as init_kaprocs (see which) */ { Lock_Init (&cheader_lock); Lock_Init (&keycache_lock); maxCachedKeys = 10; keyCache = (struct cachedKey *)malloc (maxCachedKeys * sizeof(struct cachedKey)); keyCacheVersion = 0; if (initFlags & 4) { maxKeyLifetime = 90; } else { maxKeyLifetime = MAXKTCTICKETLIFETIME; } if (initFlags & 8) dbfixup++; } /* check that the database has been initialized. Be careful to fail in a safe manner, to avoid bogusly reinitializing the db. */ afs_int32 CheckInit (at, db_init) struct ubik_trans *at; int (*db_init)(); /* procedure to call if rebuilding DB */ { register afs_int32 code; afs_int32 iversion; afs_int32 tversion; /* Don't read header if not necessary */ if (!ubik_CacheUpdate (at)) return 0; ObtainWriteLock (&cheader_lock); if ((code = karead (at, 0, (char *) &iversion, sizeof(iversion))) || (code = karead (at, sizeof(cheader)-sizeof(afs_int32), (char *) &tversion, sizeof(afs_int32)))) { if (code == UEOF) printf ("No data base\n"); else printf ("I/O Error\n"); } else { iversion = ntohl(iversion); /* convert to host order */ tversion = ntohl(tversion); if ((iversion == KADBVERSION) && (tversion == KADBVERSION)) { code = karead(at, 0, (char *) &cheader, sizeof(cheader)); if (code) { printf ("SetupHeader failed\n"); code = KAIO; } else { cheaderReadTime = time(0); } } else { printf ("DB version should be %d; Initial = %d; Terminal = %d\n", KADBVERSION, iversion, tversion); code = KAIO; } } ReleaseWriteLock (&cheader_lock); if (code == 0) return 0; /* if here, we have no version number or the wrong version number in the file */ if ((code == UEOF) || ((iversion == 0) && (tversion == 0))) code = KAEMPTY; else code = KAIO; if ((db_init == 0) || (code == KAIO)) return code; printf ("Error discovered in header, rebuilding.\n"); /* try to write a good header */ bzero(&cheader,sizeof(cheader)); cheader.version = htonl(KADBVERSION); cheader.checkVersion = htonl(KADBVERSION); cheader.headerSize = htonl(sizeof(cheader)); cheader.freePtr = 0; cheader.eofPtr = htonl(sizeof(cheader)); cheader.kvnoPtr = 0; cheader.specialKeysVersion = htonl(time(0)); /* anything non-zero will do */ cheader.stats.cpws = cheader.stats.allocs = cheader.stats.frees = 0; cheader.admin_accounts = 0; cheader.hashsize = htonl(HASHSIZE); code = kawrite(at, 0, (char *) &cheader, sizeof(cheader)); if (code) return KAIO; /* return the error code */ return db_init (at); /* initialize the db */ } /* Allocate a free block of storage for entry, returning address of a new zeroed entry. If zero is returned, a Ubik I/O error can be assumed. */ afs_int32 AllocBlock (at, tentry) register struct ubik_trans *at; struct kaentry *tentry; { register afs_int32 code; afs_int32 temp; if (cheader.freePtr) { /* allocate this dude */ temp = ntohl(cheader.freePtr); code = karead(at, temp, (char *) tentry, sizeof(kaentry)); if (code) return 0; /* can't read block */ code = set_header_word (at, freePtr, tentry->next); } else { /* hosed, nothing on free list, grow file */ temp = ntohl(cheader.eofPtr); /* remember this guy */ code = set_header_word (at, eofPtr, htonl(temp+sizeof(kaentry))); } if (code) return 0; code = inc_header_word (at, stats.allocs); if (code) return 0; bzero (tentry, sizeof(kaentry)); /* zero new entry */ return temp; } /* Free a block given its index. It must already have been unthreaded. Returns zero for success or an error code on failure. */ afs_int32 FreeBlock (at, index) struct ubik_trans *at; afs_int32 index; { struct kaentry tentry; int code; /* check index just to be on the safe side */ if (!index_OK (index)) return KABADINDEX; bzero (&tentry, sizeof(kaentry)); tentry.next = cheader.freePtr; tentry.flags = htonl(KAFFREE); code = set_header_word (at, freePtr, htonl(index)); if (code) return KAIO; code = kawrite (at, index, (char *)&tentry, sizeof(kaentry)); if (code) return KAIO; code = inc_header_word (at, stats.frees); if (code) return KAIO; return 0; } /* Look for a block by name and instance. If found read the block's contents into the area pointed to by tentry and return the block's index. If not found offset is set to zero. If an error is encountered a non-zero code is returned. */ afs_int32 FindBlock (at, aname, ainstance, toP, tentry) struct ubik_trans *at; char *aname; char *ainstance; afs_int32 *toP; struct kaentry *tentry; { register afs_int32 i, code; register afs_int32 to; *toP = 0; i = NameHash(aname, ainstance); for (to = ntohl(cheader.nameHash[i]); to != NULLO; to = ntohl(tentry->next)) { code = karead(at, to, (char *) tentry, sizeof(kaentry)); if (code) return code; /* see if the name matches */ if (!strcmp(aname, tentry->userID.name) && (ainstance == (char *) 0 || !strcmp(ainstance, tentry->userID.instance))) { *toP = to; /* found it */ return 0; } } *toP = 0; /* no such entry */ return 0; } /* Add a block to the hash table given a pointer to the block and its index. The block is threaded onto the hash table and written to disk. The routine returns zero if there were no errors. */ afs_int32 ThreadBlock (at, index, tentry) struct ubik_trans *at; afs_int32 index; struct kaentry *tentry; { int code; int hi; /* hash index */ if (!index_OK(index)) return KABADINDEX; hi = NameHash (tentry->userID.name, tentry->userID.instance); tentry->next = cheader.nameHash[hi]; code = set_header_word (at, nameHash[hi], htonl(index)); if (code) return KAIO; code = kawrite (at, index, (char *)tentry, sizeof(kaentry)); if (code) return KAIO; return 0; } /* Remove a block from the hash table. If success return 0, else return an error code. */ afs_int32 UnthreadBlock (at, aentry) struct ubik_trans *at; struct kaentry *aentry; { register afs_int32 i, code; register afs_int32 to; afs_int32 lo; struct kaentry tentry; i = NameHash (aentry->userID.name, aentry->userID.instance); lo = 0; for (to = ntohl(cheader.nameHash[i]); to != NULLO; to = ntohl(tentry.next)) { code = karead(at, to, (char *) &tentry, sizeof(kaentry)); if (code) return KAIO; /* see if the name matches */ if (!strcmp(aentry->userID.name, tentry.userID.name) && !strcmp(aentry->userID.instance, tentry.userID.instance)) { /* found it */ if (lo) { /* unthread from last block */ code = kawrite (at, lo, (char *)&tentry.next, sizeof(afs_int32)); if (code) return KAIO; } else { /* unthread from hash table */ code = set_header_word (at, nameHash[i], tentry.next); if (code) return KAIO; } aentry->next = 0; /* just to be sure */ return 0; } lo = DOFFSET(to, &tentry, &tentry.next); } return KANOENT; } /* Given an index to the last block (or zero the first time) read the contents of the next block and return its index. The last argument is a pointer to an estimate of the number of remaining blocks to read out. The remaining count is an estimate because it may include free blocks that are not returned. If there are no more blocks remaining is zero and the returned index is zero. A non-zero index indicates that tentry has been filled with valid data. If an error is encountered the returned index is zero and the remaining count is negative. */ afs_int32 NextBlock (at, index, tentry, remaining) struct ubik_trans *at; afs_int32 index; struct kaentry *tentry; afs_int32 *remaining; { int code; afs_int32 last; if (index == 0) /* get first one */ index = sizeof(cheader); else { if (!index_OK (index)) { *remaining = -1; /* error */ return 0; } index += sizeof(kaentry); } /* now search for the first entry that isn't free */ for (last = ntohl(cheader.eofPtr); index < last; index += sizeof(kaentry)) { code = karead (at, index, (char *)tentry, sizeof(kaentry)); if (code) { *remaining = -1; return 0; } if (!(ntohl(tentry->flags) & (KAFFREE|KAFOLDKEYS))) { /* estimate remaining number of entries, not including this one */ *remaining = (last - index) / sizeof(kaentry) - 1; return index; } } *remaining = 0; /* no more entries */ return 0; } /* These are a collections of routines that deal with externally known keys. They maintain a database of key version numbers and the corresponding key and pointer to the user entry. */ afs_int32 ka_NewKey (tt, tentryaddr, tentry, key) struct ubik_trans *tt; afs_int32 tentryaddr; struct kaentry *tentry; struct ktc_encryptionKey *key; { struct kaOldKeys okeys; /* old keys block */ afs_int32 okeysaddr, nextaddr; /* offset of old keys block */ afs_int32 prevptr, nextprevptr; int code, i; Date now = time(0); afs_int32 newkeyver; /* new key version number */ afs_int32 newtotalkeyentries = 0, oldtotalkeyentries = 0, keyentries; int foundcurrentkey = 0, addednewkey = 0, modified; #if !SPECIAL if ((ntohl(tentry->flags) & KAFSPECIAL) == 0) return KANOTSPECIAL; #endif es_Report ("Newkey for %s.%s\n", tentry->userID.name, tentry->userID.instance); newkeyver = ntohl(tentry->key_version) + 1; if ((newkeyver < 1) || (newkeyver >= MAXKAKVNO)) newkeyver = 1; /* An entry may have more than one oldkeys blocks. The entry * points to the most current, but all the oldkeys blocks for an * entry are not linked together. All oldkeys blocks for all * entries are linked together off of the header. So we follow * this link. */ for (prevptr = 0, okeysaddr = ntohl(cheader.kvnoPtr); okeysaddr; prevptr = nextprevptr, okeysaddr = nextaddr) { /* foreacholdkeysblock */ /* Read the oldKeys block */ code = karead (tt, okeysaddr, (char *)&okeys, sizeof(okeys)); if (code) return code; nextaddr = ntohl(okeys.next); nextprevptr = DOFFSET(okeysaddr, &okeys, &okeys.next); /* We only want oldkey blocks that belong to this entry */ if (ntohl(okeys.entry) != tentryaddr) continue; modified = 0; /* This oldkeys block has not been modified */ keyentries = 0; /* Number of valid key entries in the block */ for (i=0; iuserID.name, tentry->userID.instance)); } #endif foundcurrentkey = 1; } /* If we find an oldkey of the same version or * an old key that has expired, then delete it. */ if ( (ntohl(okeys.keys[i].version) == newkeyver) || ((now - ntohl(okeys.keys[i].superseded) > maxKeyLifetime)) ) { okeys.keys[i].superseded = 0; okeys.keys[i].version = htonl(-1); bzero(&okeys.keys[i].key, sizeof(struct ktc_encryptionKey)); modified = 1; es_Report ("Dropped oldkey %d seconds old with kvno %d\n", now - ntohl(okeys.keys[i].superseded), ntohl(okeys.keys[i].version)); } /* Add our key here if its free */ if (!addednewkey && (okeys.keys[i].superseded == 0)) { okeys.keys[i].version = htonl(newkeyver); okeys.keys[i].superseded = htonl(NEVERDATE); bcopy (key, &okeys.keys[i].key, sizeof(struct ktc_encryptionKey)); modified = 1; addednewkey = okeysaddr; } /* Keep count of number of entries found */ if (okeys.keys[i].superseded != 0) { keyentries++; newtotalkeyentries++; } } /* foreachkey */ /* If we modified the block, write it out */ if (modified && keyentries) { code = kawrite (tt, okeysaddr, (char *)&okeys, sizeof(okeys)); if (code) return code; } /* If there are no more entries in this oldkeys block, delete it */ if (keyentries == 0) { if (!prevptr) { code = set_header_word(tt, kvnoPtr, okeys.next); } else { code = kawrite(tt, prevptr, (char *)&okeys.next, sizeof(afs_int32)); } if (code) return code; code = FreeBlock (tt, okeysaddr); if (code) return code; nextprevptr = prevptr; /* won't bump prevptr */ } } /* foreacholdkeysblock */ /* If we could not add the key, create a new oldkeys block */ if (!addednewkey) { /* Allocate and fill in an oldkeys block */ addednewkey = AllocBlock(tt, (struct kaentry *)&okeys); if (!addednewkey) return KACREATEFAIL; okeys.flags = htonl(KAFOLDKEYS); okeys.entry = htonl(tentryaddr); okeys.keys[0].version = htonl(newkeyver); okeys.keys[0].superseded = htonl(NEVERDATE); bcopy (key, &okeys.keys[0].key, sizeof(struct ktc_encryptionKey)); newtotalkeyentries++; /* Thread onto the header's chain of oldkeys */ okeys.next = cheader.kvnoPtr; code = set_header_word(tt, kvnoPtr, htonl(addednewkey)); if (code) return code; /* Write the oldkeys block out */ code = kawrite (tt, addednewkey, (char *)&okeys, sizeof(okeys)); if (code) return code; es_Report ("New oldkey block allocated at %d\n", addednewkey); } #ifdef AUTH_DBM_LOG if (oldtotalkeyentries != ntohl(tentry->misc.asServer.nOldKeys)) { ViceLog(0,("Warning: Entry %s.%s reports %d oldkeys, found %d: fixing\n", tentry->userID.name, tentry->userID.instance, ntohl(tentry->misc.asServer.nOldKeys), oldtotalkeyentries)); } #endif /* Update the tentry. We rely on caller to write it out */ tentry->misc.asServer.oldKeys = htonl(addednewkey); tentry->misc.asServer.nOldKeys = htonl(newtotalkeyentries); tentry->key_version = htonl(newkeyver); bcopy (key, &tentry->key, sizeof (tentry->key)); /* invalidate key caches everywhere */ code = inc_header_word (tt, specialKeysVersion); if (code) return code; es_Report ("New kvno is %d, now are %d oldkeys\n", newkeyver, newtotalkeyentries); return 0; } afs_int32 ka_DelKey (tt, tentryaddr, tentry) struct ubik_trans *tt; afs_int32 tentryaddr; struct kaentry *tentry; { int code, i; struct kaOldKeys okeys; /* old keys block */ afs_int32 okeysaddr, nextaddr; /* offset of old keys block */ afs_int32 prevptr = 0; Date now = time(0); #if !SPECIAL if ((ntohl(tentry->flags) & KAFSPECIAL) == 0) return KANOTSPECIAL; #endif es_Report ("DelKey for %s.%s\n", tentry->userID.name, tentry->userID.instance); /* An entry may have more than one oldkeys blocks. The entry * points to the most current, but all the oldkeys blocks for an * entry are not linked together. All oldkeys blocks for all * entries are linked together off of the header. So we follow * this link. */ for (okeysaddr=ntohl(cheader.kvnoPtr); okeysaddr; okeysaddr=nextaddr) { /* foreacholdkeysblock */ /* Read the oldKeys block */ code = karead (tt, okeysaddr, (char *)&okeys, sizeof(okeys)); if (code) return code; nextaddr = ntohl(okeys.next); /* We only want oldkey blocks that belong to this entry */ if (ntohl(okeys.entry) != tentryaddr) { prevptr = DOFFSET(okeysaddr, &okeys, &okeys.next); continue; } /* Delete the oldkeys block */ if (prevptr) { code = kawrite(tt, prevptr, (char *)&okeys.next, sizeof(afs_int32)); } else { code = set_header_word(tt, kvnoPtr, okeys.next); } if (code) return code; code = FreeBlock (tt, okeysaddr); if (code) return code; } /* foreacholdkeysblock */ /* Update the tentry. We rely on caller to write it out */ tentry->misc.asServer.oldKeys = 0; tentry->misc.asServer.nOldKeys = 0; /* invalidate key caches everywhere */ code = inc_header_word (tt, specialKeysVersion); if (code) return code; return 0; } void ka_debugKeyCache (info) struct ka_debugInfo *info; { int i; bcopy (&cheader_lock, &info->cheader_lock, sizeof (info->cheader_lock)); bcopy (&keycache_lock, &info->keycache_lock, sizeof (info->keycache_lock)); info->kcVersion = keyCacheVersion; info->kcSize = maxCachedKeys; info->kcUsed = 0; for (i=0; ikcUsed < KADEBUGKCINFOSIZE) { int j = info->kcUsed; int k; char principal[sizeof(keyCache[0].name) + sizeof(keyCache[0].inst)]; info->kcInfo[j].used = keyCache[i].superseded; info->kcInfo[j].kvno = keyCache[i].kvno; info->kcInfo[j].primary = (keyCache[i].superseded == NEVERDATE); info->kcInfo[j].keycksum = 0; #if DEBUG_KEY_CACHE for (k=0; kkcInfo[j].keycksum += ((char *)&keyCache[i].key)[k]; #endif strcpy (principal, keyCache[i].name); strcat (principal, "."); strcat (principal, keyCache[i].inst); strncpy (info->kcInfo[j].principal, principal, sizeof (info->kcInfo[0].principal)); } info->kcUsed++; } } } /* Add a key to the key cache, expanding it if necessary. */ ka_Encache (name, inst, kvno, key, superseded) char *name; char *inst; afs_int32 kvno; struct ktc_encryptionKey *key; Date superseded; { int i; ObtainWriteLock (&keycache_lock); if (keyCacheVersion != ntohl(cheader.specialKeysVersion)) { for (i=0; i maxCachedKeys) return KADATABASEINCONSISTENT; return 0; } afs_int32 update_admin_count (tt, delta) struct ubik_trans *tt; int delta; { afs_int32 to; afs_int32 code; cheader.admin_accounts = htonl(ntohl(cheader.admin_accounts) + delta); to = DOFFSET (0, &cheader, &cheader.admin_accounts); code = kawrite (tt, to, (char *)&cheader.admin_accounts, sizeof(afs_int32)); if (code) return KAIO; return 0; } static int index_OK (index) afs_int32 index; { if ((index < sizeof(cheader)) || (index >= ntohl(cheader.eofPtr)) || ((index - sizeof(cheader)) % sizeof(kaentry) != 0)) return 0; return 1; } #define LEGALCHARS ".ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" int name_instance_legal (name, instance) char *name; char *instance; { int code; /* No string checks apply anymore. The international people want to use full 8 bit ascii without problems. */ #if 1 code = (strlen(name) < MAXKTCNAMELEN) && (strlen(instance) < MAXKTCNAMELEN); #else map = LEGALCHARS; /* permitted chars, instance allows */ code = (strlen (name) > 0) && string_legal (instance, map) && string_legal (name, map+1); #endif if (!code) dynamic_statistics.string_checks++; return code; } static int string_legal (str, map) char *str; char *map; { int slen; slen = strlen (str); if (slen >= MAXKTCNAMELEN) return 0; /* with trailing null must fit in data base */ return (slen == strspn (str, map)); /* strspn returns length(str) if all chars in map */ }