2 * Copyright (c) 1997 - 2008 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
6 * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the Institute nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36 #include "krb5_locl.h"
43 /*! @mainpage Heimdal database backend library
45 * @section intro Introduction
47 * Heimdal libhdb library provides the backend support for Heimdal kdc
48 * and kadmind. Its here where plugins for diffrent database engines
49 * can be pluged in and extend support for here Heimdal get the
50 * principal and policy data from.
52 * Example of Heimdal backend are:
60 * The project web page: http://www.h5l.org/
64 const int hdb_interface_version
= HDB_INTERFACE_VERSION
;
66 static struct hdb_method methods
[] = {
67 /* "db:" should be db3 if we have db3, or db1 if we have db1 */
69 { HDB_INTERFACE_VERSION
, NULL
, NULL
, 1 /*is_file_based*/, 1 /*can_taste*/,
70 "db:", hdb_db3_create
},
72 { HDB_INTERFACE_VERSION
, NULL
, NULL
, 1, 1, "db:", hdb_db1_create
},
75 { HDB_INTERFACE_VERSION
, NULL
, NULL
, 1, 1, "db1:", hdb_db1_create
},
78 { HDB_INTERFACE_VERSION
, NULL
, NULL
, 1, 1, "db3:", hdb_db3_create
},
81 { HDB_INTERFACE_VERSION
, NULL
, NULL
, 1, 1, "mit-db:", hdb_mitdb_create
},
84 { HDB_INTERFACE_VERSION
, NULL
, NULL
, 1, 1, "mdb:", hdb_mdb_create
},
85 { HDB_INTERFACE_VERSION
, NULL
, NULL
, 1, 1, "lmdb:", hdb_mdb_create
},
88 { HDB_INTERFACE_VERSION
, NULL
, NULL
, 1, 0, "ndbm:", hdb_ndbm_create
},
91 { HDB_INTERFACE_VERSION
, NULL
, NULL
, 1, 1, "sqlite:", hdb_sqlite_create
},
93 /* The keytab interface can't use its hdb_open() method to "taste" a DB */
94 { HDB_INTERFACE_VERSION
, NULL
, NULL
, 1, 0, "keytab:", hdb_keytab_create
},
95 /* The rest are not file-based */
96 #if defined(OPENLDAP) && !defined(OPENLDAP_MODULE)
97 { HDB_INTERFACE_VERSION
, NULL
, NULL
, 0, 0, "ldap:", hdb_ldap_create
},
98 { HDB_INTERFACE_VERSION
, NULL
, NULL
, 0, 0, "ldapi:", hdb_ldapi_create
},
99 #elif defined(OPENLDAP)
100 { HDB_INTERFACE_VERSION
, NULL
, NULL
, 0, 0, "ldap:", NULL
},
101 { HDB_INTERFACE_VERSION
, NULL
, NULL
, 0, 0, "ldapi:", NULL
},
103 { 0, NULL
, NULL
, 0, 0, NULL
, NULL
}
107 * Returns the Keys of `e' for `kvno', or NULL if not found. The Keys will
108 * remain valid provided that the entry is not mutated.
110 * @param context Context
111 * @param e The HDB entry
112 * @param kvno The kvno
114 * @return A pointer to the Keys for the requested kvno.
117 hdb_kvno2keys(krb5_context context
,
121 HDB_Ext_KeySet
*hist_keys
;
125 if (kvno
== 0 || e
->kvno
== kvno
)
128 extp
= hdb_find_extension(e
, choice_HDB_extension_data_hist_keys
);
132 hist_keys
= &extp
->data
.u
.hist_keys
;
133 for (i
= 0; i
< hist_keys
->len
; i
++) {
134 if (hist_keys
->val
[i
].kvno
== kvno
)
135 return &hist_keys
->val
[i
].keys
;
141 /* Based on remove_HDB_Ext_KeySet(), generated by the ASN.1 compiler */
143 dequeue_HDB_Ext_KeySet(HDB_Ext_KeySet
*data
, unsigned int element
, hdb_keyset
*ks
)
145 if (element
>= data
->len
) {
152 *ks
= data
->val
[element
];
154 /* Swap instead of memmove()... changes the order of elements */
155 if (element
< data
->len
)
156 data
->val
[element
] = data
->val
[data
->len
];
157 if (data
->len
== 0) {
166 * Removes from `e' and optionally outputs the keyset for the requested `kvno'.
168 * @param context Context
169 * @param e The HDB entry
170 * @param kvno The key version number
171 * @param ks A pointer to a variable of type hdb_keyset (may be NULL)
173 * @return Zero on success, an error code otherwise.
176 hdb_remove_keys(krb5_context context
,
181 HDB_Ext_KeySet
*hist_keys
;
185 if (kvno
== 0 || e
->kvno
== kvno
) {
189 (void) hdb_entry_get_pw_change_time(e
, &t
);
191 if ((ks
->set_time
= malloc(sizeof(*ks
->set_time
))) == NULL
)
192 return krb5_enomem(context
);
213 extp
= hdb_find_extension(e
, choice_HDB_extension_data_hist_keys
);
217 hist_keys
= &extp
->data
.u
.hist_keys
;
218 for (i
= 0; i
< hist_keys
->len
; i
++) {
219 if (hist_keys
->val
[i
].kvno
!= kvno
)
222 return dequeue_HDB_Ext_KeySet(hist_keys
, i
, ks
);
223 return remove_HDB_Ext_KeySet(hist_keys
, i
);
225 return HDB_ERR_NOENTRY
;
229 * Removes from `e' and outputs all the base keys for virtual principal and/or
232 * @param context Context
233 * @param e The HDB entry
234 * @param ks A pointer to a variable of type HDB_Ext_KeySet
235 * @param ckr A pointer to stable (copied) HDB_Ext_KeyRotation
237 * @return Zero on success, an error code otherwise.
240 _hdb_remove_base_keys(krb5_context context
,
242 HDB_Ext_KeySet
*base_keys
,
243 const HDB_Ext_KeyRotation
*ckr
)
245 krb5_error_code ret
= 0;
249 if ((base_keys
->val
= calloc(ckr
->len
, sizeof(base_keys
->val
[0]))) == NULL
)
250 ret
= krb5_enomem(context
);
252 for (k
= i
= 0; ret
== 0 && i
< ckr
->len
; i
++) {
253 const KeyRotation
*krp
= &ckr
->val
[i
];
256 * WARNING: O(N * M) where M is number of keysets and N is the number
259 * In practice N will never be > 3 because the ASN.1 module imposes
260 * that as a constraint, and M will generally be the same as N, so this
261 * will be O(1) after all.
263 ret
= hdb_remove_keys(context
, e
, krp
->base_key_kvno
,
267 else if (ret
== HDB_ERR_NOENTRY
)
273 free_HDB_Ext_KeySet(base_keys
);
278 * Removes from `e' and outputs all the base keys for virtual principal and/or
281 * @param context Context
282 * @param e The HDB entry
283 * @param is_current_keyset Whether to make the keys the current keys for `e'
284 * @param ks A pointer to an hdb_keyset containing the keys to set
286 * @return Zero on success, an error code otherwise.
289 hdb_install_keyset(krb5_context context
,
291 int is_current_keyset
,
292 const hdb_keyset
*ks
)
294 krb5_error_code ret
= 0;
296 if (is_current_keyset
) {
298 (ret
= hdb_add_current_keys_to_history(context
, e
)))
303 ret
= copy_Keys(&ks
->keys
, &e
->keys
);
304 if (ret
== 0 && ks
->set_time
)
305 ret
= hdb_entry_set_pw_change_time(context
, e
, *ks
->set_time
);
308 return hdb_add_history_keyset(context
, e
, ks
);
313 hdb_next_enctype2key(krb5_context context
,
316 krb5_enctype enctype
,
319 const Keys
*keys
= keyset
? keyset
: &e
->keys
;
322 for (k
= *key
? (*key
) + 1 : keys
->val
; k
< keys
->val
+ keys
->len
; k
++) {
323 if(k
->key
.keytype
== enctype
){
328 krb5_set_error_message(context
, KRB5_PROG_ETYPE_NOSUPP
,
329 "No next enctype %d for hdb-entry",
331 return KRB5_PROG_ETYPE_NOSUPP
; /* XXX */
335 hdb_enctype2key(krb5_context context
,
338 krb5_enctype enctype
,
342 return hdb_next_enctype2key(context
, e
, keyset
, enctype
, key
);
346 hdb_free_key(Key
*key
)
348 memset_s(key
->key
.keyvalue
.data
,
349 key
->key
.keyvalue
.length
,
351 key
->key
.keyvalue
.length
);
358 hdb_lock(int fd
, int operation
)
362 for(i
= 0; i
< 3; i
++){
363 code
= flock(fd
, (operation
== HDB_RLOCK
? LOCK_SH
: LOCK_EX
) | LOCK_NB
);
364 if(code
== 0 || errno
!= EWOULDBLOCK
)
370 if(errno
== EWOULDBLOCK
)
371 return HDB_ERR_DB_INUSE
;
372 return HDB_ERR_CANT_LOCK_DB
;
379 code
= flock(fd
, LOCK_UN
);
381 return 4711 /* XXX */;
386 hdb_free_entry(krb5_context context
, HDB
*db
, hdb_entry
*ent
)
391 if (db
&& db
->hdb_free_entry_context
)
392 db
->hdb_free_entry_context(context
, db
, ent
);
394 for(i
= 0; i
< ent
->keys
.len
; i
++) {
395 k
= &ent
->keys
.val
[i
];
397 memset_s(k
->key
.keyvalue
.data
,
398 k
->key
.keyvalue
.length
,
400 k
->key
.keyvalue
.length
);
406 hdb_foreach(krb5_context context
,
409 hdb_foreach_func_t func
,
414 ret
= db
->hdb_firstkey(context
, db
, flags
, &entry
);
416 krb5_clear_error_message(context
);
418 ret
= (*func
)(context
, db
, &entry
, data
);
419 hdb_free_entry(context
, db
, &entry
);
421 ret
= db
->hdb_nextkey(context
, db
, flags
, &entry
);
423 if(ret
== HDB_ERR_NOENTRY
)
429 hdb_check_db_format(krb5_context context
, HDB
*db
)
433 krb5_error_code ret
, ret2
;
437 ret
= db
->hdb_lock(context
, db
, HDB_RLOCK
);
441 tag
.data
= (void *)(intptr_t)HDB_DB_FORMAT_ENTRY
;
442 tag
.length
= strlen(tag
.data
);
443 ret
= (*db
->hdb__get
)(context
, db
, tag
, &version
);
444 ret2
= db
->hdb_unlock(context
, db
);
449 foo
= sscanf(version
.data
, "%u", &ver
);
450 krb5_data_free (&version
);
452 return HDB_ERR_BADVERSION
;
453 if(ver
!= HDB_DB_FORMAT
)
454 return HDB_ERR_BADVERSION
;
459 hdb_init_db(krb5_context context
, HDB
*db
)
461 krb5_error_code ret
, ret2
;
466 ret
= hdb_check_db_format(context
, db
);
467 if(ret
!= HDB_ERR_NOENTRY
)
470 ret
= db
->hdb_lock(context
, db
, HDB_WLOCK
);
474 tag
.data
= (void *)(intptr_t)HDB_DB_FORMAT_ENTRY
;
475 tag
.length
= strlen(tag
.data
);
476 snprintf(ver
, sizeof(ver
), "%u", HDB_DB_FORMAT
);
478 version
.length
= strlen(version
.data
) + 1; /* zero terminated */
479 ret
= (*db
->hdb__put
)(context
, db
, 0, tag
, version
);
480 ret2
= db
->hdb_unlock(context
, db
);
483 krb5_clear_error_message(context
);
490 * `default_dbmethod' is the last resort default.
492 * In hdb_create() we may try all the `methods[]' until one succeeds or all
495 #if defined(HAVE_LMDB)
496 static struct hdb_method default_dbmethod
=
497 { HDB_INTERFACE_VERSION
, NULL
, NULL
, 1, 1, "", hdb_mdb_create
};
498 #elif defined(HAVE_DB3)
499 static struct hdb_method default_dbmethod
=
500 { HDB_INTERFACE_VERSION
, NULL
, NULL
, 1, 1, "", hdb_db3_create
};
501 #elif defined(HAVE_DB1)
502 static struct hdb_method default_dbmethod
=
503 { HDB_INTERFACE_VERSION
, NULL
, NULL
, 1, 1, "", hdb_db1_create
};
504 #elif defined(HAVE_NDBM)
505 static struct hdb_method default_dbmethod
=
506 { HDB_INTERFACE_VERSION
, NULL
, NULL
, 0, 1, "", hdb_ndbm_create
};
508 static struct hdb_method default_dbmethod
=
509 { 0, NULL
, NULL
, 0, 0, NULL
, NULL
};
513 is_pathish(const char *s
)
516 strncmp(s
, "./", sizeof("./") - 1) == 0 ||
517 strncmp(s
, "../", sizeof("../") - 1) == 0)
520 if (s
[0] == '\\' || (isalpha((unsigned char)s
[0]) && s
[0] == ':') ||
521 strncmp(s
, ".\\", sizeof(".\\") - 1) == 0 ||
522 strncmp(s
, "\\\\", sizeof("\\\\") - 1) == 0)
528 static const struct hdb_method
*
529 has_method_prefix(const char *filename
)
531 const struct hdb_method
*h
;
533 for (h
= methods
; h
->prefix
!= NULL
; ++h
)
534 if (strncmp(filename
, h
->prefix
, strlen(h
->prefix
)) == 0)
540 * find the relevant method for `filename', returning a pointer to the
542 * return NULL if there's no such method.
545 static const struct hdb_method
*
546 find_method(const char *filename
, const char **rest
)
548 const struct hdb_method
*h
= has_method_prefix(filename
);
550 *rest
= h
? filename
+ strlen(h
->prefix
) : filename
;
555 const char *residual
;
556 const char *filename
;
557 const struct hdb_method
*h
;
560 static krb5_error_code KRB5_LIB_CALL
561 callback(krb5_context context
, const void *plug
, void *plugctx
, void *userctx
)
563 const struct hdb_method
*h
= (const struct hdb_method
*)plug
;
564 struct cb_s
*cb_ctx
= (struct cb_s
*)userctx
;
566 if (strncmp(cb_ctx
->filename
, h
->prefix
, strlen(h
->prefix
)) == 0) {
567 cb_ctx
->residual
= cb_ctx
->filename
+ strlen(h
->prefix
) + 1;
571 return KRB5_PLUGIN_NO_HANDLE
;
575 make_sym(const char *prefix
)
580 if (prefix
== NULL
|| prefix
[0] == '\0')
582 if ((s
= strdup(prefix
)) == NULL
)
584 if (strchr(s
, ':') != NULL
)
585 *strchr(s
, ':') = '\0';
586 if (asprintf(&sym
, "hdb_%s_interface", s
) == -1)
592 static const char *hdb_plugin_deps
[] = { "hdb", "krb5", NULL
};
595 hdb_list_builtin(krb5_context context
, char **list
)
597 const struct hdb_method
*h
;
601 for (h
= methods
; h
->prefix
!= NULL
; ++h
) {
602 if (h
->prefix
[0] == '\0')
604 len
+= strlen(h
->prefix
) + 2;
610 return krb5_enomem(context
);
614 for (h
= methods
; h
->prefix
!= NULL
; ++h
) {
615 if (h
->create
== NULL
) {
618 struct heim_plugin_data hdb_plugin_data
;
620 hdb_plugin_data
.module
= "krb5";
621 hdb_plugin_data
.min_version
= HDB_INTERFACE_VERSION
;
622 hdb_plugin_data
.deps
= hdb_plugin_deps
;
623 hdb_plugin_data
.get_instance
= hdb_get_instance
;
625 /* Try loading the plugin */
626 if (asprintf(&f
, "%sfoo", h
->prefix
) == -1)
628 if ((hdb_plugin_data
.name
= make_sym(h
->prefix
)) == NULL
) {
631 return krb5_enomem(context
);
634 cb_ctx
.residual
= NULL
;
636 (void)_krb5_plugin_run_f(context
, &hdb_plugin_data
, 0,
639 free(rk_UNCONST(hdb_plugin_data
.name
));
640 if (cb_ctx
.h
== NULL
|| cb_ctx
.h
->create
== NULL
)
644 strlcat(buf
, ", ", len
);
645 strlcat(buf
, h
->prefix
, len
);
652 _hdb_keytab2hdb_entry(krb5_context context
,
653 const krb5_keytab_entry
*ktentry
,
656 entry
->kvno
= ktentry
->vno
;
657 entry
->created_by
.time
= ktentry
->timestamp
;
659 entry
->keys
.val
= calloc(1, sizeof(entry
->keys
.val
[0]));
660 if (entry
->keys
.val
== NULL
)
664 entry
->keys
.val
[0].mkvno
= NULL
;
665 entry
->keys
.val
[0].salt
= NULL
;
667 return krb5_copy_keyblock_contents(context
,
669 &entry
->keys
.val
[0].key
);
672 static krb5_error_code
673 load_config(krb5_context context
, HDB
*db
)
675 db
->enable_virtual_hostbased_princs
=
676 krb5_config_get_bool_default(context
, NULL
, FALSE
, "hdb",
677 "enable_virtual_hostbased_princs",
679 db
->virtual_hostbased_princ_ndots
=
680 krb5_config_get_int_default(context
, NULL
, 1, "hdb",
681 "virtual_hostbased_princ_mindots",
683 db
->virtual_hostbased_princ_maxdots
=
684 krb5_config_get_int_default(context
, NULL
, 0, "hdb",
685 "virtual_hostbased_princ_maxdots",
687 db
->new_service_key_delay
=
688 krb5_config_get_time_default(context
, NULL
, 0, "hdb",
689 "new_service_key_delay", NULL
);
691 * XXX Needs freeing in the HDB backends because we don't have a
692 * first-class hdb_close() :(
694 db
->virtual_hostbased_princ_svcs
=
695 krb5_config_get_strings(context
, NULL
, "hdb",
696 "virtual_hostbased_princ_svcs", NULL
);
697 /* Check for ENOMEM */
698 if (db
->virtual_hostbased_princ_svcs
== NULL
699 && krb5_config_get_string(context
, NULL
, "hdb",
700 "virtual_hostbased_princ_svcs", NULL
)) {
701 return krb5_enomem(context
);
707 * Create a handle for a Kerberos database
709 * Create a handle for a Kerberos database backend specified by a
710 * filename. Doesn't actually create or even open an HDB file(s);
711 * you have to call the hdb_open() open method of the resulting HDB
712 * to open the database, and you have to use O_CREAT to create it.
714 * If `filename' does not have a backend type prefix, all file-based
715 * backends will be tried until one succeeds or all fail, and if the
716 * HDB exists for some backend, that will be used. A build-time
717 * default backend type will be used if the `filename' does not exist.
719 * Note that the actual filename may have a suffix added, such as
720 * ".db". Also, for backends such as "ldap:" and "ldapi:" the
721 * `filename' is more like a URI.
723 * @param [in] context Context
724 * @param [out] db HDB handle output
725 * @param [in] filename The name of the HDB
727 * @return Zero on success else a krb5 error code.
731 hdb_create(krb5_context context
, HDB
**db
, const char *filename
)
733 krb5_error_code ret
= ENOTSUP
;
737 if (filename
== NULL
)
738 filename
= hdb_default_db(context
);
740 cb_ctx
.h
= find_method(filename
, &cb_ctx
.residual
);
741 cb_ctx
.filename
= filename
;
743 if (cb_ctx
.h
== NULL
|| cb_ctx
.h
->create
== NULL
) {
744 struct heim_plugin_data hdb_plugin_data
;
747 * `filename' does not start with a known HDB backend prefix.
751 hdb_plugin_data
.module
= "krb5";
752 hdb_plugin_data
.min_version
= HDB_INTERFACE_VERSION
;
753 hdb_plugin_data
.deps
= hdb_plugin_deps
;
754 hdb_plugin_data
.get_instance
= hdb_get_instance
;
756 if ((hdb_plugin_data
.name
= make_sym(filename
)) == NULL
)
757 return krb5_enomem(context
);
759 (void)_krb5_plugin_run_f(context
, &hdb_plugin_data
, 0 /* flags */,
762 free(rk_UNCONST(hdb_plugin_data
.name
));
765 if (cb_ctx
.h
== NULL
|| cb_ctx
.h
->create
== NULL
) {
766 int pathish
= is_pathish(filename
);
768 * `filename' does not start with a known HDB backend prefix and it
769 * wasn't handled by any plugin.
771 * If it's "filename-ish", try all builtin HDB backends that are
772 * local-file-ish, but use hdb_open() to see if the HDB exists and stop
773 * when a backend is found for which the HDB exists.
776 krb5_set_error_message(context
, ret
= ENOTSUP
,
777 "No database support for %s",
781 for (cb_ctx
.h
= methods
; cb_ctx
.h
->prefix
!= NULL
; cb_ctx
.h
++) {
782 if (cb_ctx
.h
->is_file_based
)
784 if (!cb_ctx
.h
->can_taste
)
787 ret
= (*cb_ctx
.h
->create
)(context
, db
, filename
);
789 ret
= (*db
)->hdb_open(context
, *db
, O_RDONLY
, 0);
791 (void) (*db
)->hdb_close(context
, *db
);
795 (*db
)->hdb_destroy(context
, *db
);
798 if (cb_ctx
.h
->prefix
== NULL
)
801 #ifdef HDB_DEFAULT_DB_TYPE
802 if (cb_ctx
.h
== NULL
) {
804 * If still we've not picked a backend, use a build configuration time
807 for (cb_ctx
.h
= methods
; cb_ctx
.h
->prefix
!= NULL
; cb_ctx
.h
++)
808 if (strcmp(cb_ctx
.h
->prefix
, HDB_DEFAULT_DB_TYPE
) == 0)
810 if (cb_ctx
.h
->prefix
== NULL
)
814 if (cb_ctx
.h
== NULL
)
815 /* Last resort default */
816 cb_ctx
.h
= &default_dbmethod
;
817 if (cb_ctx
.h
->prefix
== NULL
) {
818 krb5_set_error_message(context
, ENOTSUP
,
819 "Could not determine default DB backend for %s",
824 ret
= (*cb_ctx
.h
->create
)(context
, db
, cb_ctx
.residual
);
826 (*db
)->hdb_method_name
= cb_ctx
.h
->prefix
;
829 ret
= load_config(context
, *db
);
831 (*db
)->hdb_destroy(context
, *db
);
837 uintptr_t KRB5_CALLCONV
838 hdb_get_instance(const char *libname
)
840 static const char *instance
= "libhdb";
842 if (strcmp(libname
, "hdb") == 0)
843 return (uintptr_t)instance
;
844 else if (strcmp(libname
, "krb5") == 0)
845 return krb5_get_instance(libname
);