summaryrefslogtreecommitdiff
path: root/source4/heimdal/kdc
diff options
context:
space:
mode:
authorAndrew Bartlett <abartlet@samba.org>2010-01-12 18:16:45 +1100
committerAndrew Bartlett <abartlet@samba.org>2010-03-27 11:51:27 +1100
commit89eaef025376339ef25d07cdc4748920fceaa968 (patch)
treef514f4632c9d54a372a7f1f0ca845a0c3a488fbf /source4/heimdal/kdc
parentfac8ca52ade6e490eea3cf3d0fc98287da321c13 (diff)
downloadsamba-89eaef025376339ef25d07cdc4748920fceaa968.tar.gz
s4:heimdal: import lorikeet-heimdal-201001120029 (commit a5e675fed7c5db8a7370b77ed0bfa724196aa84d)
Diffstat (limited to 'source4/heimdal/kdc')
-rw-r--r--source4/heimdal/kdc/default_config.c119
-rw-r--r--source4/heimdal/kdc/headers.h3
-rw-r--r--source4/heimdal/kdc/kdc.h8
-rw-r--r--source4/heimdal/kdc/kdc_locl.h4
-rw-r--r--source4/heimdal/kdc/kerberos5.c168
-rw-r--r--source4/heimdal/kdc/krb5tgs.c5
-rw-r--r--source4/heimdal/kdc/kx509.c18
-rw-r--r--source4/heimdal/kdc/log.c7
-rw-r--r--source4/heimdal/kdc/pkinit.c31
9 files changed, 197 insertions, 166 deletions
diff --git a/source4/heimdal/kdc/default_config.c b/source4/heimdal/kdc/default_config.c
index bf65af3cb9e..b568522fa49 100644
--- a/source4/heimdal/kdc/default_config.c
+++ b/source4/heimdal/kdc/default_config.c
@@ -1,9 +1,10 @@
/*
* Copyright (c) 1997-2007 Kungliga Tekniska Högskolan
* (Royal Institute of Technology, Stockholm, Sweden).
- *
* All rights reserved.
*
+ * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
+ *
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
@@ -215,7 +216,6 @@ krb5_kdc_get_config(krb5_context context, krb5_kdc_configuration **config)
"kdc", "kdc_warn_pwexpire", NULL);
-#ifdef PKINIT
c->enable_pkinit =
krb5_config_get_bool_default(context,
NULL,
@@ -223,74 +223,73 @@ krb5_kdc_get_config(krb5_context context, krb5_kdc_configuration **config)
"kdc",
"enable-pkinit",
NULL);
- if (c->enable_pkinit) {
- const char *user_id, *anchors, *file;
- char **pool_list, **revoke_list;
-
- user_id =
- krb5_config_get_string(context, NULL,
- "kdc", "pkinit_identity", NULL);
- if (user_id == NULL)
- krb5_errx(context, 1, "pkinit enabled but no identity");
- anchors = krb5_config_get_string(context, NULL,
- "kdc", "pkinit_anchors", NULL);
- if (anchors == NULL)
- krb5_errx(context, 1, "pkinit enabled but no X509 anchors");
- pool_list =
- krb5_config_get_strings(context, NULL,
- "kdc", "pkinit_pool", NULL);
+ c->pkinit_kdc_identity =
+ krb5_config_get_string(context, NULL,
+ "kdc", "pkinit_identity", NULL);
+ c->pkinit_kdc_anchors =
+ krb5_config_get_string(context, NULL,
+ "kdc", "pkinit_anchors", NULL);
+ c->pkinit_kdc_cert_pool =
+ krb5_config_get_strings(context, NULL,
+ "kdc", "pkinit_pool", NULL);
+ c->pkinit_kdc_revoke =
+ krb5_config_get_strings(context, NULL,
+ "kdc", "pkinit_revoke", NULL);
+ c->pkinit_kdc_ocsp_file =
+ krb5_config_get_string(context, NULL,
+ "kdc", "pkinit_kdc_ocsp", NULL);
+ c->pkinit_kdc_friendly_name =
+ krb5_config_get_string(context, NULL,
+ "kdc", "pkinit_kdc_friendly_name", NULL);
+ c->pkinit_princ_in_cert =
+ krb5_config_get_bool_default(context, NULL,
+ c->pkinit_princ_in_cert,
+ "kdc",
+ "pkinit_principal_in_certificate",
+ NULL);
+ c->pkinit_require_binding =
+ krb5_config_get_bool_default(context, NULL,
+ c->pkinit_require_binding,
+ "kdc",
+ "pkinit_win2k_require_binding",
+ NULL);
+ c->pkinit_dh_min_bits =
+ krb5_config_get_int_default(context, NULL,
+ 0,
+ "kdc", "pkinit_dh_min_bits", NULL);
- revoke_list =
- krb5_config_get_strings(context, NULL,
- "kdc", "pkinit_revoke", NULL);
- file = krb5_config_get_string(context, NULL,
- "kdc", "pkinit_kdc_ocsp", NULL);
- if (file) {
- c->pkinit_kdc_ocsp_file = strdup(file);
- if (c->pkinit_kdc_ocsp_file == NULL)
- krb5_errx(context, 1, "out of memory");
- }
-
- file = krb5_config_get_string(context, NULL,
- "kdc", "pkinit_kdc_friendly_name", NULL);
- if (file) {
- c->pkinit_kdc_friendly_name = strdup(file);
- if (c->pkinit_kdc_friendly_name == NULL)
- krb5_errx(context, 1, "out of memory");
- }
+#ifdef __APPLE__
+ c->enable_pkinit = 1;
+ if (c->pkinit_kdc_identity == NULL) {
+ if (c->pkinit_kdc_friendly_name == NULL)
+ c->pkinit_kdc_friendly_name =
+ strdup("O=System Identity,CN=com.apple.kerberos.kdc");
+ c->pkinit_kdc_identity = strdup("KEYCHAIN:");
+ }
+ if (c->pkinit_kdc_anchors == NULL)
+ c->pkinit_kdc_anchors = strdup("KEYCHAIN:");
- _kdc_pk_initialize(context, c, user_id, anchors,
- pool_list, revoke_list);
+#endif
- krb5_config_free_strings(pool_list);
- krb5_config_free_strings(revoke_list);
+ if (c->enable_pkinit) {
+ if (c->pkinit_kdc_identity == NULL)
+ krb5_errx(context, 1, "pkinit enabled but no identity");
+
+ if (c->pkinit_kdc_anchors == NULL)
+ krb5_errx(context, 1, "pkinit enabled but no X509 anchors");
- c->pkinit_princ_in_cert =
- krb5_config_get_bool_default(context, NULL,
- c->pkinit_princ_in_cert,
- "kdc",
- "pkinit_principal_in_certificate",
- NULL);
+ krb5_kdc_pk_initialize(context, c,
+ c->pkinit_kdc_identity,
+ c->pkinit_kdc_anchors,
+ c->pkinit_kdc_cert_pool,
+ c->pkinit_kdc_revoke);
- c->pkinit_require_binding =
- krb5_config_get_bool_default(context, NULL,
- c->pkinit_require_binding,
- "kdc",
- "pkinit_win2k_require_binding",
- NULL);
}
-
- c->pkinit_dh_min_bits =
- krb5_config_get_int_default(context, NULL,
- 0,
- "kdc", "pkinit_dh_min_bits", NULL);
-
-#endif
-
+
*config = c;
return 0;
diff --git a/source4/heimdal/kdc/headers.h b/source4/heimdal/kdc/headers.h
index b9a828852a6..1eb3ddedcd8 100644
--- a/source4/heimdal/kdc/headers.h
+++ b/source4/heimdal/kdc/headers.h
@@ -38,9 +38,8 @@
#ifndef __HEADERS_H__
#define __HEADERS_H__
-#ifdef HAVE_CONFIG_H
#include <config.h>
-#endif
+
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
diff --git a/source4/heimdal/kdc/kdc.h b/source4/heimdal/kdc/kdc.h
index 285a33af14a..c353ca1c5f6 100644
--- a/source4/heimdal/kdc/kdc.h
+++ b/source4/heimdal/kdc/kdc.h
@@ -74,8 +74,12 @@ typedef struct krb5_kdc_configuration {
krb5_boolean enable_pkinit;
krb5_boolean pkinit_princ_in_cert;
- char *pkinit_kdc_ocsp_file;
- char *pkinit_kdc_friendly_name;
+ const char *pkinit_kdc_identity;
+ const char *pkinit_kdc_anchors;
+ const char *pkinit_kdc_friendly_name;
+ const char *pkinit_kdc_ocsp_file;
+ char **pkinit_kdc_cert_pool;
+ char **pkinit_kdc_revoke;
int pkinit_dh_min_bits;
int pkinit_require_binding;
int pkinit_allow_proxy_certs;
diff --git a/source4/heimdal/kdc/kdc_locl.h b/source4/heimdal/kdc/kdc_locl.h
index f2da03b5e6f..36d694dae5f 100644
--- a/source4/heimdal/kdc/kdc_locl.h
+++ b/source4/heimdal/kdc/kdc_locl.h
@@ -77,4 +77,8 @@ loop(krb5_context context, krb5_kdc_configuration *config);
krb5_kdc_configuration *
configure(krb5_context context, int argc, char **argv);
+#ifdef __APPLE__
+void bonjour_announce(krb5_context, krb5_kdc_configuration *);
+#endif
+
#endif /* __KDC_LOCL_H__ */
diff --git a/source4/heimdal/kdc/kerberos5.c b/source4/heimdal/kdc/kerberos5.c
index fb88aa9f8fb..87162d5f98e 100644
--- a/source4/heimdal/kdc/kerberos5.c
+++ b/source4/heimdal/kdc/kerberos5.c
@@ -60,13 +60,13 @@ realloc_method_data(METHOD_DATA *md)
}
static void
-set_salt_padata (METHOD_DATA *md, Salt *salt)
+set_salt_padata(METHOD_DATA *md, Salt *salt)
{
if (salt) {
- realloc_method_data(md);
- md->val[md->len - 1].padata_type = salt->type;
- der_copy_octet_string(&salt->salt,
- &md->val[md->len - 1].padata_value);
+ realloc_method_data(md);
+ md->val[md->len - 1].padata_type = salt->type;
+ der_copy_octet_string(&salt->salt,
+ &md->val[md->len - 1].padata_value);
}
}
@@ -127,7 +127,7 @@ is_default_salt_p(const krb5_salt *default_salt, const Key *key)
krb5_error_code
_kdc_find_etype(krb5_context context, const hdb_entry_ex *princ,
krb5_enctype *etypes, unsigned len,
- Key **ret_key, krb5_enctype *ret_etype)
+ Key **ret_key)
{
int i;
krb5_error_code ret = KRB5KDC_ERR_ETYPE_NOSUPP;
@@ -148,7 +148,6 @@ _kdc_find_etype(krb5_context context, const hdb_entry_ex *princ,
continue;
}
*ret_key = key;
- *ret_etype = etypes[i];
ret = 0;
if (is_default_salt_p(&def_salt, key)) {
krb5_free_salt (context, def_salt);
@@ -287,8 +286,9 @@ _kdc_encode_reply(krb5_context context,
ret = krb5_crypto_init(context, skey, etype, &crypto);
if (ret) {
+ const char *msg;
free(buf);
- const char *msg = krb5_get_error_message(context, ret);
+ msg = krb5_get_error_message(context, ret);
kdc_log(context, config, 0, "krb5_crypto_init failed: %s", msg);
krb5_free_error_message(context, msg);
return ret;
@@ -902,7 +902,7 @@ _kdc_as_rep(krb5_context context,
KDCOptions f = b->kdc_options;
hdb_entry_ex *client = NULL, *server = NULL;
HDB *clientdb;
- krb5_enctype cetype, setype, sessionetype;
+ krb5_enctype setype, sessionetype;
krb5_data e_data;
EncTicketPart et;
EncKDCRepPart ek;
@@ -912,15 +912,20 @@ _kdc_as_rep(krb5_context context,
const char *e_text = NULL;
krb5_crypto crypto;
Key *ckey, *skey;
- EncryptionKey *reply_key;
+ EncryptionKey *reply_key, session_key;
int flags = 0;
#ifdef PKINIT
pk_client_params *pkp = NULL;
#endif
memset(&rep, 0, sizeof(rep));
+ memset(&session_key, 0, sizeof(session_key));
krb5_data_zero(&e_data);
+ ALLOC(rep.padata);
+ rep.padata->len = 0;
+ rep.padata->val = NULL;
+
if (f.canonicalize)
flags |= HDB_F_CANON;
@@ -1009,18 +1014,58 @@ _kdc_as_rep(krb5_context context,
memset(&ek, 0, sizeof(ek));
/*
- * Find the client key for reply encryption and pa-type salt, Pick
- * the client key upfront before the other keys because that is
- * going to affect what enctypes we are going to use in
- * ETYPE-INFO{,2}.
+ * Select a session enctype from the list of the crypto systems
+ * supported enctype, is supported by the client and is one of the
+ * enctype of the enctype of the krbtgt.
+ *
+ * The later is used as a hint what enctype all KDC are supporting
+ * to make sure a newer version of KDC wont generate a session
+ * enctype that and older version of a KDC in the same realm can't
+ * decrypt.
+ *
+ * But if the KDC admin is paranoid and doesn't want to have "no
+ * the best" enctypes on the krbtgt, lets save the best pick from
+ * the client list and hope that that will work for any other
+ * KDCs.
*/
+ {
+ const krb5_enctype *p;
+ krb5_enctype clientbest = ETYPE_NULL;
+ int i, j;
- ret = _kdc_find_etype(context, client, b->etype.val, b->etype.len,
- &ckey, &cetype);
- if (ret) {
- kdc_log(context, config, 0,
- "Client (%s) has no support for etypes", client_name);
- goto out;
+ p = krb5_kerberos_enctypes(context);
+
+ sessionetype = ETYPE_NULL;
+
+ for (i = 0; p[i] != ETYPE_NULL && sessionetype == ETYPE_NULL; i++) {
+ if (krb5_enctype_valid(context, p[i]) != 0)
+ continue;
+
+ for (j = 0; j < b->etype.len && sessionetype == ETYPE_NULL; j++) {
+ Key *dummy;
+ /* check with client */
+ if (p[i] != b->etype.val[j])
+ continue;
+ /* save best of union of { client, crypto system } */
+ if (clientbest == ETYPE_NULL)
+ clientbest = p[i];
+ /* check with krbtgt */
+ ret = hdb_enctype2key(context, &server->entry, p[i], &dummy);
+ if (ret)
+ continue;
+ sessionetype = p[i];
+ }
+ }
+ /* if krbtgt had no shared keys with client, pick clients best */
+ if (clientbest != ETYPE_NULL && sessionetype == ETYPE_NULL) {
+ sessionetype = clientbest;
+ } else if (sessionetype == ETYPE_NULL) {
+ kdc_log(context, config, 0,
+ "Client (%s) from %s has no common enctypes with KDC"
+ "to use for the session key",
+ client_name, from);
+ goto out;
+ }
}
/*
@@ -1230,7 +1275,11 @@ _kdc_as_rep(krb5_context context,
}
et.flags.pre_authent = 1;
- ret = krb5_enctype_to_string(context,pa_key->key.keytype, &str);
+ set_salt_padata(rep.padata, pa_key->salt);
+
+ reply_key = &pa_key->key;
+
+ ret = krb5_enctype_to_string(context, pa_key->key.keytype, &str);
if (ret)
str = NULL;
@@ -1300,7 +1349,9 @@ _kdc_as_rep(krb5_context context,
/*
* If there is a client key, send ETYPE_INFO{,2}
*/
- if (ckey) {
+ ret = _kdc_find_etype(context, client, b->etype.val, b->etype.len,
+ &ckey);
+ if (ret == 0) {
/*
* RFC4120 requires:
@@ -1371,63 +1422,6 @@ _kdc_as_rep(krb5_context context,
if(ret)
goto out;
- /*
- * Select a session enctype from the list of the crypto systems
- * supported enctype, is supported by the client and is one of the
- * enctype of the enctype of the krbtgt.
- *
- * The later is used as a hint what enctype all KDC are supporting
- * to make sure a newer version of KDC wont generate a session
- * enctype that and older version of a KDC in the same realm can't
- * decrypt.
- *
- * But if the KDC admin is paranoid and doesn't want to have "no
- * the best" enctypes on the krbtgt, lets save the best pick from
- * the client list and hope that that will work for any other
- * KDCs.
- */
- {
- const krb5_enctype *p;
- krb5_enctype clientbest = ETYPE_NULL;
- int i, j;
-
- p = krb5_kerberos_enctypes(context);
-
- sessionetype = ETYPE_NULL;
-
- for (i = 0; p[i] != ETYPE_NULL && sessionetype == ETYPE_NULL; i++) {
- if (krb5_enctype_valid(context, p[i]) != 0)
- continue;
-
- for (j = 0; j < b->etype.len && sessionetype == ETYPE_NULL; j++) {
- Key *dummy;
- /* check with client */
- if (p[i] != b->etype.val[j])
- continue;
- /* save best of union of { client, crypto system } */
- if (clientbest == ETYPE_NULL)
- clientbest = p[i];
- /* check with krbtgt */
- ret = hdb_enctype2key(context, &server->entry, p[i], &dummy);
- if (ret)
- continue;
- sessionetype = p[i];
- }
- }
- /* if krbtgt had no shared keys with client, pick clients best */
- if (clientbest != ETYPE_NULL && sessionetype == ETYPE_NULL) {
- sessionetype = clientbest;
- } else if (sessionetype == ETYPE_NULL) {
- kdc_log(context, config, 0,
- "Client (%s) from %s has no common enctypes with KDC"
- "to use for the session key",
- client_name, from);
- goto out;
- }
- }
-
- log_as_req(context, config, cetype, setype, b);
-
if(f.renew || f.validate || f.proxy || f.forwarded || f.enc_tkt_in_skey
|| (f.request_anonymous && !config->allow_anonymous)) {
ret = KRB5KDC_ERR_BADOPTION;
@@ -1622,10 +1616,6 @@ _kdc_as_rep(krb5_context context,
copy_HostAddresses(et.caddr, ek.caddr);
}
- ALLOC(rep.padata);
- rep.padata->len = 0;
- rep.padata->val = NULL;
-
#if PKINIT
if (pkp) {
e_text = "Failed to build PK-INIT reply";
@@ -1642,12 +1632,13 @@ _kdc_as_rep(krb5_context context,
goto out;
} else
#endif
- if (ckey) {
- reply_key = &ckey->key;
+ {
ret = krb5_generate_random_keyblock(context, sessionetype, &et.key);
if (ret)
goto out;
- } else {
+ }
+
+ if (reply_key == NULL) {
e_text = "Client have no reply key";
ret = KRB5KDC_ERR_CLIENT_NOTYET;
goto out;
@@ -1657,9 +1648,6 @@ _kdc_as_rep(krb5_context context,
if (ret)
goto out;
- if (ckey)
- set_salt_padata (rep.padata, ckey->salt);
-
/* Add signing of alias referral */
if (f.canonicalize) {
PA_ClientCanonicalized canon;
@@ -1765,6 +1753,8 @@ _kdc_as_rep(krb5_context context,
if (ret)
goto out;
+ log_as_req(context, config, reply_key->keytype, setype, b);
+
ret = _kdc_encode_reply(context, config,
&rep, &et, &ek, setype, server->entry.kvno,
&skey->key, client->entry.kvno,
diff --git a/source4/heimdal/kdc/krb5tgs.c b/source4/heimdal/kdc/krb5tgs.c
index 312be6e016d..b6f9c865bbd 100644
--- a/source4/heimdal/kdc/krb5tgs.c
+++ b/source4/heimdal/kdc/krb5tgs.c
@@ -1633,14 +1633,15 @@ server_lookup:
} else {
Key *skey;
- ret = _kdc_find_etype(context, server, b->etype.val, b->etype.len,
- &skey, &etype);
+ ret = _kdc_find_etype(context, server,
+ b->etype.val, b->etype.len, &skey);
if(ret) {
kdc_log(context, config, 0,
"Server (%s) has no support for etypes", spn);
goto out;
}
ekey = &skey->key;
+ etype = skey->key.keytype;
kvno = server->entry.kvno;
}
diff --git a/source4/heimdal/kdc/kx509.c b/source4/heimdal/kdc/kx509.c
index eb757bb5786..f6f8f8a3bd6 100644
--- a/source4/heimdal/kdc/kx509.c
+++ b/source4/heimdal/kdc/kx509.c
@@ -345,10 +345,24 @@ _kdc_do_kx509(krb5_context context,
ret = krb5_principal_compare(context, sprincipal, principal);
krb5_free_principal(context, principal);
if (ret != TRUE) {
+ char *expected, *used;
+
+ ret = krb5_unparse_name(context, sprincipal, &expected);
+ if (ret)
+ goto out;
+ ret = krb5_unparse_name(context, principal, &used);
+ if (ret) {
+ krb5_xfree(expected);
+ goto out;
+ }
+
ret = KRB5KDC_ERR_SERVER_NOMATCH;
krb5_set_error_message(context, ret,
- "User %s used wrong Kx509 service principal",
- cname);
+ "User %s used wrong Kx509 service "
+ "principal, expected: %s, used %s",
+ cname, expected, used);
+ krb5_xfree(expected);
+ krb5_xfree(used);
goto out;
}
}
diff --git a/source4/heimdal/kdc/log.c b/source4/heimdal/kdc/log.c
index b4161da45d0..06e64df840a 100644
--- a/source4/heimdal/kdc/log.c
+++ b/source4/heimdal/kdc/log.c
@@ -3,6 +3,8 @@
* (Royal Institute of Technology, Stockholm, Sweden).
* All rights reserved.
*
+ * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
+ *
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
@@ -36,13 +38,14 @@ RCSID("$Id$");
void
kdc_openlog(krb5_context context,
+ const char *service,
krb5_kdc_configuration *config)
{
char **s = NULL, **p;
krb5_initlog(context, "kdc", &config->logf);
- s = krb5_config_get_strings(context, NULL, "kdc", "logging", NULL);
+ s = krb5_config_get_strings(context, NULL, service, "logging", NULL);
if(s == NULL)
- s = krb5_config_get_strings(context, NULL, "logging", "kdc", NULL);
+ s = krb5_config_get_strings(context, NULL, "logging", service, NULL);
if(s){
for(p = s; *p; p++)
krb5_addlog_dest(context, config->logf, *p);
diff --git a/source4/heimdal/kdc/pkinit.c b/source4/heimdal/kdc/pkinit.c
index 7bb32eb5770..099d3ebe7df 100644
--- a/source4/heimdal/kdc/pkinit.c
+++ b/source4/heimdal/kdc/pkinit.c
@@ -3,6 +3,8 @@
* (Royal Institute of Technology, Stockholm, Sweden).
* All rights reserved.
*
+ * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
+ *
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
@@ -1379,7 +1381,22 @@ _kdc_pk_mk_pa_reply(krb5_context context,
}
- ASN1_MALLOC_ENCODE(PA_PK_AS_REP, buf, len, &rep, &size, ret);
+#define use_btmm_with_enckey 0
+ if (use_btmm_with_enckey && rep.element == choice_PA_PK_AS_REP_encKeyPack) {
+ PA_PK_AS_REP_BTMM btmm;
+ heim_any any;
+
+ any.data = rep.u.encKeyPack.data;
+ any.length = rep.u.encKeyPack.length;
+
+ btmm.dhSignedData = NULL;
+ btmm.encKeyPack = &any;
+
+ ASN1_MALLOC_ENCODE(PA_PK_AS_REP_BTMM, buf, len, &btmm, &size, ret);
+ } else {
+ ASN1_MALLOC_ENCODE(PA_PK_AS_REP, buf, len, &rep, &size, ret);
+ }
+
free_PA_PK_AS_REP(&rep);
if (ret) {
krb5_set_error_message(context, ret,
@@ -1928,12 +1945,12 @@ load_mappings(krb5_context context, const char *fn)
*/
krb5_error_code
-_kdc_pk_initialize(krb5_context context,
- krb5_kdc_configuration *config,
- const char *user_id,
- const char *anchors,
- char **pool,
- char **revoke_list)
+krb5_kdc_pk_initialize(krb5_context context,
+ krb5_kdc_configuration *config,
+ const char *user_id,
+ const char *anchors,
+ char **pool,
+ char **revoke_list)
{
const char *file;
char *fn = NULL;