155682Smarkm/*
2233294Sstas * Copyright (c) 1999 - 2002 Kungliga Tekniska H��gskolan
3233294Sstas * (Royal Institute of Technology, Stockholm, Sweden).
4233294Sstas * All rights reserved.
555682Smarkm *
6233294Sstas * Redistribution and use in source and binary forms, with or without
7233294Sstas * modification, are permitted provided that the following conditions
8233294Sstas * are met:
955682Smarkm *
10233294Sstas * 1. Redistributions of source code must retain the above copyright
11233294Sstas *    notice, this list of conditions and the following disclaimer.
1255682Smarkm *
13233294Sstas * 2. Redistributions in binary form must reproduce the above copyright
14233294Sstas *    notice, this list of conditions and the following disclaimer in the
15233294Sstas *    documentation and/or other materials provided with the distribution.
1655682Smarkm *
17233294Sstas * 3. Neither the name of the Institute nor the names of its contributors
18233294Sstas *    may be used to endorse or promote products derived from this software
19233294Sstas *    without specific prior written permission.
2055682Smarkm *
21233294Sstas * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22233294Sstas * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23233294Sstas * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24233294Sstas * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25233294Sstas * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26233294Sstas * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27233294Sstas * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28233294Sstas * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29233294Sstas * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30233294Sstas * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31233294Sstas * SUCH DAMAGE.
3255682Smarkm */
3355682Smarkm
3455682Smarkm#include "hdb_locl.h"
3555682Smarkm
3655682Smarkm/* keytab backend for HDB databases */
3755682Smarkm
3855682Smarkmstruct hdb_data {
3955682Smarkm    char *dbname;
4055682Smarkm    char *mkey;
4155682Smarkm};
4255682Smarkm
43233294Sstasstruct hdb_cursor {
44233294Sstas    HDB *db;
45233294Sstas    hdb_entry_ex hdb_entry;
46233294Sstas    int first, next;
47233294Sstas    int key_idx;
48233294Sstas};
49233294Sstas
5072445Sassar/*
5172445Sassar * the format for HDB keytabs is:
52233294Sstas * HDB:[HDBFORMAT:database-specific-data[:mkey=mkey-file]]
5372445Sassar */
5472445Sassar
55233294Sstasstatic krb5_error_code KRB5_CALLCONV
5655682Smarkmhdb_resolve(krb5_context context, const char *name, krb5_keytab id)
5755682Smarkm{
5855682Smarkm    struct hdb_data *d;
5955682Smarkm    const char *db, *mkey;
6072445Sassar
6155682Smarkm    d = malloc(sizeof(*d));
6290926Snectar    if(d == NULL) {
63233294Sstas	krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
6455682Smarkm	return ENOMEM;
6590926Snectar    }
6655682Smarkm    db = name;
67233294Sstas    mkey = strstr(name, ":mkey=");
68233294Sstas    if(mkey == NULL || mkey[5] == '\0') {
6955682Smarkm	if(*name == '\0')
7055682Smarkm	    d->dbname = NULL;
7155682Smarkm	else {
7255682Smarkm	    d->dbname = strdup(name);
7355682Smarkm	    if(d->dbname == NULL) {
7455682Smarkm		free(d);
75233294Sstas		krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
7655682Smarkm		return ENOMEM;
7755682Smarkm	    }
7855682Smarkm	}
7955682Smarkm	d->mkey = NULL;
8055682Smarkm    } else {
81233294Sstas	d->dbname = malloc(mkey - db + 1);
82233294Sstas	if(d->dbname == NULL) {
83233294Sstas	    free(d);
84233294Sstas	    krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
85233294Sstas	    return ENOMEM;
8655682Smarkm	}
87233294Sstas	memmove(d->dbname, db, mkey - db);
88233294Sstas	d->dbname[mkey - db] = '\0';
89233294Sstas
90233294Sstas	d->mkey = strdup(mkey + 5);
9155682Smarkm	if(d->mkey == NULL) {
9255682Smarkm	    free(d->dbname);
9355682Smarkm	    free(d);
94233294Sstas	    krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
9555682Smarkm	    return ENOMEM;
9655682Smarkm	}
9755682Smarkm    }
9855682Smarkm    id->data = d;
9955682Smarkm    return 0;
10055682Smarkm}
10155682Smarkm
102233294Sstasstatic krb5_error_code KRB5_CALLCONV
10355682Smarkmhdb_close(krb5_context context, krb5_keytab id)
10455682Smarkm{
10555682Smarkm    struct hdb_data *d = id->data;
10672445Sassar
10772445Sassar    free(d->dbname);
10872445Sassar    free(d->mkey);
10955682Smarkm    free(d);
11055682Smarkm    return 0;
11155682Smarkm}
11255682Smarkm
113233294Sstasstatic krb5_error_code KRB5_CALLCONV
114233294Sstashdb_get_name(krb5_context context,
115233294Sstas	     krb5_keytab id,
116233294Sstas	     char *name,
11755682Smarkm	     size_t namesize)
11855682Smarkm{
11955682Smarkm    struct hdb_data *d = id->data;
12072445Sassar
121233294Sstas    snprintf(name, namesize, "%s%s%s",
12255682Smarkm	     d->dbname ? d->dbname : "",
12355682Smarkm	     (d->dbname || d->mkey) ? ":" : "",
12455682Smarkm	     d->mkey ? d->mkey : "");
12555682Smarkm    return 0;
12655682Smarkm}
12755682Smarkm
12872445Sassar/*
12972445Sassar * try to figure out the database (`dbname') and master-key (`mkey')
13072445Sassar * that should be used for `principal'.
13172445Sassar */
13272445Sassar
133233294Sstasstatic krb5_error_code
13472445Sassarfind_db (krb5_context context,
135233294Sstas	 char **dbname,
136233294Sstas	 char **mkey,
13772445Sassar	 krb5_const_principal principal)
13872445Sassar{
139233294Sstas    krb5_const_realm realm = krb5_principal_get_realm(context, principal);
140233294Sstas    krb5_error_code ret;
141233294Sstas    struct hdb_dbinfo *head, *dbinfo = NULL;
14272445Sassar
14372445Sassar    *dbname = *mkey = NULL;
14472445Sassar
145233294Sstas    ret = hdb_get_dbinfo(context, &head);
146233294Sstas    if (ret)
147233294Sstas	return ret;
148233294Sstas
149233294Sstas    while ((dbinfo = hdb_dbinfo_get_next(head, dbinfo)) != NULL) {
150233294Sstas	const char *p = hdb_dbinfo_get_realm(context, dbinfo);
151233294Sstas	if (p && strcmp (realm, p) == 0) {
152233294Sstas	    p = hdb_dbinfo_get_dbname(context, dbinfo);
153233294Sstas	    if (p)
154233294Sstas		*dbname = strdup(p);
155233294Sstas	    p = hdb_dbinfo_get_mkey_file(context, dbinfo);
156233294Sstas	    if (p)
157233294Sstas		*mkey = strdup(p);
15872445Sassar	    break;
15972445Sassar	}
16072445Sassar    }
161233294Sstas    hdb_free_dbinfo(context, &head);
16272445Sassar    if (*dbname == NULL)
163233294Sstas	*dbname = strdup(HDB_DEFAULT_DB);
164233294Sstas    return 0;
16572445Sassar}
16672445Sassar
16772445Sassar/*
16872445Sassar * find the keytab entry in `id' for `principal, kvno, enctype' and return
16972445Sassar * it in `entry'.  return 0 or an error code
17072445Sassar */
17172445Sassar
172233294Sstasstatic krb5_error_code KRB5_CALLCONV
17355682Smarkmhdb_get_entry(krb5_context context,
17455682Smarkm	      krb5_keytab id,
17555682Smarkm	      krb5_const_principal principal,
17655682Smarkm	      krb5_kvno kvno,
17755682Smarkm	      krb5_enctype enctype,
17855682Smarkm	      krb5_keytab_entry *entry)
17955682Smarkm{
180178825Sdfr    hdb_entry_ex ent;
18155682Smarkm    krb5_error_code ret;
18255682Smarkm    struct hdb_data *d = id->data;
18372445Sassar    const char *dbname = d->dbname;
18472445Sassar    const char *mkey   = d->mkey;
185233294Sstas    char *fdbname = NULL, *fmkey = NULL;
186233294Sstas    HDB *db;
187233294Sstas    size_t i;
18855682Smarkm
189178825Sdfr    memset(&ent, 0, sizeof(ent));
190178825Sdfr
191233294Sstas    if (dbname == NULL) {
192233294Sstas	ret = find_db(context, &fdbname, &fmkey, principal);
193233294Sstas	if (ret)
194233294Sstas	    return ret;
195233294Sstas	dbname = fdbname;
196233294Sstas	mkey = fmkey;
197233294Sstas    }
19872445Sassar
19972445Sassar    ret = hdb_create (context, &db, dbname);
20055682Smarkm    if (ret)
201233294Sstas	goto out2;
20272445Sassar    ret = hdb_set_master_keyfile (context, db, mkey);
20372445Sassar    if (ret) {
204178825Sdfr	(*db->hdb_destroy)(context, db);
205233294Sstas	goto out2;
20672445Sassar    }
207233294Sstas
208178825Sdfr    ret = (*db->hdb_open)(context, db, O_RDONLY, 0);
20972445Sassar    if (ret) {
210178825Sdfr	(*db->hdb_destroy)(context, db);
211233294Sstas	goto out2;
21272445Sassar    }
21372445Sassar
214233294Sstas    ret = (*db->hdb_fetch_kvno)(context, db, principal,
215233294Sstas				HDB_F_DECRYPT|HDB_F_KVNO_SPECIFIED|
216233294Sstas				HDB_F_GET_CLIENT|HDB_F_GET_SERVER|HDB_F_GET_KRBTGT,
217233294Sstas				kvno, &ent);
218233294Sstas
219178825Sdfr    if(ret == HDB_ERR_NOENTRY) {
220178825Sdfr	ret = KRB5_KT_NOTFOUND;
221178825Sdfr	goto out;
222178825Sdfr    }else if(ret)
223178825Sdfr	goto out;
224178825Sdfr
225233294Sstas    if(kvno && (krb5_kvno)ent.entry.kvno != kvno) {
22655682Smarkm	hdb_free_entry(context, &ent);
227178825Sdfr 	ret = KRB5_KT_NOTFOUND;
228178825Sdfr	goto out;
22955682Smarkm    }
23055682Smarkm    if(enctype == 0)
231178825Sdfr	if(ent.entry.keys.len > 0)
232178825Sdfr	    enctype = ent.entry.keys.val[0].key.keytype;
23355682Smarkm    ret = KRB5_KT_NOTFOUND;
234178825Sdfr    for(i = 0; i < ent.entry.keys.len; i++) {
235178825Sdfr	if(ent.entry.keys.val[i].key.keytype == enctype) {
23655682Smarkm	    krb5_copy_principal(context, principal, &entry->principal);
237178825Sdfr	    entry->vno = ent.entry.kvno;
238233294Sstas	    krb5_copy_keyblock_contents(context,
239233294Sstas					&ent.entry.keys.val[i].key,
24055682Smarkm					&entry->keyblock);
24155682Smarkm	    ret = 0;
24255682Smarkm	    break;
24355682Smarkm	}
24455682Smarkm    }
24555682Smarkm    hdb_free_entry(context, &ent);
246233294Sstas out:
247178825Sdfr    (*db->hdb_close)(context, db);
248178825Sdfr    (*db->hdb_destroy)(context, db);
249233294Sstas out2:
250233294Sstas    free(fdbname);
251233294Sstas    free(fmkey);
25255682Smarkm    return ret;
25355682Smarkm}
25455682Smarkm
255233294Sstas/*
256233294Sstas * find the keytab entry in `id' for `principal, kvno, enctype' and return
257233294Sstas * it in `entry'.  return 0 or an error code
258233294Sstas */
259233294Sstas
260233294Sstasstatic krb5_error_code KRB5_CALLCONV
261233294Sstashdb_start_seq_get(krb5_context context,
262233294Sstas		  krb5_keytab id,
263233294Sstas		  krb5_kt_cursor *cursor)
264233294Sstas{
265233294Sstas    krb5_error_code ret;
266233294Sstas    struct hdb_cursor *c;
267233294Sstas    struct hdb_data *d = id->data;
268233294Sstas    const char *dbname = d->dbname;
269233294Sstas    const char *mkey   = d->mkey;
270233294Sstas    HDB *db;
271233294Sstas
272233294Sstas    if (dbname == NULL) {
273233294Sstas	/*
274233294Sstas	 * We don't support enumerating without being told what
275233294Sstas	 * backend to enumerate on
276233294Sstas	 */
277233294Sstas  	ret = KRB5_KT_NOTFOUND;
278233294Sstas	return ret;
279233294Sstas    }
280233294Sstas
281233294Sstas    ret = hdb_create (context, &db, dbname);
282233294Sstas    if (ret)
283233294Sstas	return ret;
284233294Sstas    ret = hdb_set_master_keyfile (context, db, mkey);
285233294Sstas    if (ret) {
286233294Sstas	(*db->hdb_destroy)(context, db);
287233294Sstas	return ret;
288233294Sstas    }
289233294Sstas
290233294Sstas    ret = (*db->hdb_open)(context, db, O_RDONLY, 0);
291233294Sstas    if (ret) {
292233294Sstas	(*db->hdb_destroy)(context, db);
293233294Sstas	return ret;
294233294Sstas    }
295233294Sstas
296233294Sstas    cursor->data = c = malloc (sizeof(*c));
297233294Sstas    if(c == NULL){
298233294Sstas	(*db->hdb_close)(context, db);
299233294Sstas	(*db->hdb_destroy)(context, db);
300233294Sstas	krb5_set_error_message(context, ENOMEM, "malloc: out of memory");
301233294Sstas	return ENOMEM;
302233294Sstas    }
303233294Sstas
304233294Sstas    c->db = db;
305233294Sstas    c->first = TRUE;
306233294Sstas    c->next = TRUE;
307233294Sstas    c->key_idx = 0;
308233294Sstas
309233294Sstas    cursor->data = c;
310233294Sstas    return ret;
311233294Sstas}
312233294Sstas
313233294Sstasstatic int KRB5_CALLCONV
314233294Sstashdb_next_entry(krb5_context context,
315233294Sstas	       krb5_keytab id,
316233294Sstas	       krb5_keytab_entry *entry,
317233294Sstas	       krb5_kt_cursor *cursor)
318233294Sstas{
319233294Sstas    struct hdb_cursor *c = cursor->data;
320233294Sstas    krb5_error_code ret;
321233294Sstas
322233294Sstas    memset(entry, 0, sizeof(*entry));
323233294Sstas
324233294Sstas    if (c->first) {
325233294Sstas	c->first = FALSE;
326233294Sstas	ret = (c->db->hdb_firstkey)(context, c->db,
327233294Sstas				    HDB_F_DECRYPT|
328233294Sstas				    HDB_F_GET_CLIENT|HDB_F_GET_SERVER|HDB_F_GET_KRBTGT,
329233294Sstas				    &c->hdb_entry);
330233294Sstas	if (ret == HDB_ERR_NOENTRY)
331233294Sstas	    return KRB5_KT_END;
332233294Sstas	else if (ret)
333233294Sstas	    return ret;
334233294Sstas
335233294Sstas	if (c->hdb_entry.entry.keys.len == 0)
336233294Sstas	    hdb_free_entry(context, &c->hdb_entry);
337233294Sstas	else
338233294Sstas	    c->next = FALSE;
339233294Sstas    }
340233294Sstas
341233294Sstas    while (c->next) {
342233294Sstas	ret = (c->db->hdb_nextkey)(context, c->db,
343233294Sstas				   HDB_F_DECRYPT|
344233294Sstas				   HDB_F_GET_CLIENT|HDB_F_GET_SERVER|HDB_F_GET_KRBTGT,
345233294Sstas				   &c->hdb_entry);
346233294Sstas	if (ret == HDB_ERR_NOENTRY)
347233294Sstas	    return KRB5_KT_END;
348233294Sstas	else if (ret)
349233294Sstas	    return ret;
350233294Sstas
351233294Sstas	/* If no keys on this entry, try again */
352233294Sstas	if (c->hdb_entry.entry.keys.len == 0)
353233294Sstas	    hdb_free_entry(context, &c->hdb_entry);
354233294Sstas	else
355233294Sstas	    c->next = FALSE;
356233294Sstas    }
357233294Sstas
358233294Sstas    /*
359233294Sstas     * Return next enc type (keytabs are one slot per key, while
360233294Sstas     * hdb is one record per principal.
361233294Sstas     */
362233294Sstas
363233294Sstas    ret = krb5_copy_principal(context,
364233294Sstas			      c->hdb_entry.entry.principal,
365233294Sstas			      &entry->principal);
366233294Sstas    if (ret)
367233294Sstas	return ret;
368233294Sstas
369233294Sstas    entry->vno = c->hdb_entry.entry.kvno;
370233294Sstas    ret = krb5_copy_keyblock_contents(context,
371233294Sstas				      &c->hdb_entry.entry.keys.val[c->key_idx].key,
372233294Sstas				      &entry->keyblock);
373233294Sstas    if (ret) {
374233294Sstas	krb5_free_principal(context, entry->principal);
375233294Sstas	memset(entry, 0, sizeof(*entry));
376233294Sstas	return ret;
377233294Sstas    }
378233294Sstas    c->key_idx++;
379233294Sstas
380233294Sstas    /*
381233294Sstas     * Once we get to the end of the list, signal that we want the
382233294Sstas     * next entry
383233294Sstas     */
384233294Sstas
385233294Sstas    if ((size_t)c->key_idx == c->hdb_entry.entry.keys.len) {
386233294Sstas	hdb_free_entry(context, &c->hdb_entry);
387233294Sstas	c->next = TRUE;
388233294Sstas	c->key_idx = 0;
389233294Sstas    }
390233294Sstas
391233294Sstas    return 0;
392233294Sstas}
393233294Sstas
394233294Sstas
395233294Sstasstatic int KRB5_CALLCONV
396233294Sstashdb_end_seq_get(krb5_context context,
397233294Sstas		krb5_keytab id,
398233294Sstas		krb5_kt_cursor *cursor)
399233294Sstas{
400233294Sstas    struct hdb_cursor *c = cursor->data;
401233294Sstas
402233294Sstas    if (!c->next)
403233294Sstas	hdb_free_entry(context, &c->hdb_entry);
404233294Sstas
405233294Sstas    (c->db->hdb_close)(context, c->db);
406233294Sstas    (c->db->hdb_destroy)(context, c->db);
407233294Sstas
408233294Sstas    free(c);
409233294Sstas    return 0;
410233294Sstas}
411233294Sstas
41255682Smarkmkrb5_kt_ops hdb_kt_ops = {
41355682Smarkm    "HDB",
41455682Smarkm    hdb_resolve,
41555682Smarkm    hdb_get_name,
41655682Smarkm    hdb_close,
417233294Sstas    NULL,		/* destroy */
41855682Smarkm    hdb_get_entry,
419233294Sstas    hdb_start_seq_get,
420233294Sstas    hdb_next_entry,
421233294Sstas    hdb_end_seq_get,
42255682Smarkm    NULL,		/* add */
42355682Smarkm    NULL		/* remove */
42455682Smarkm};
425