]> git.ipfire.org Git - thirdparty/krb5.git/commitdiff
Merge trunk changes from r24202 to r24258 to plugins2 branch
authorGreg Hudson <ghudson@mit.edu>
Thu, 26 Aug 2010 23:19:40 +0000 (23:19 +0000)
committerGreg Hudson <ghudson@mit.edu>
Thu, 26 Aug 2010 23:19:40 +0000 (23:19 +0000)
git-svn-id: svn://anonsvn.mit.edu/krb5/branches/plugins2@24259 dc483132-0cff-0310-8789-dd5450dbe970

28 files changed:
doc/krb5conf.texinfo
src/Makefile.in
src/config-files/krb5.conf.M
src/include/Makefile.in
src/include/k5-int.h
src/include/krb5/krb5.hin
src/kdc/do_as_req.c
src/lib/gssapi/generic/util_canonhost.c [deleted file]
src/lib/gssapi/generic/util_localhost.c [deleted file]
src/lib/gssapi/krb5/acquire_cred.c
src/lib/gssapi/krb5/krb5_gss_glue.c
src/lib/kadm5/kadm_rpc_xdr.c
src/lib/krb5/error_tables/Makefile.in
src/lib/krb5/error_tables/k5e1_err.et [new file with mode: 0644]
src/lib/krb5/krb/Makefile.in
src/lib/krb5/krb/gic_opt.c
src/lib/krb5/krb/gic_pwd.c
src/lib/krb5/krb/rd_req_dec.c
src/lib/krb5/krb/t_expire_warn.c [new file with mode: 0644]
src/lib/krb5/krb/t_expire_warn.py [new file with mode: 0644]
src/lib/krb5/libkrb5.exports
src/lib/rpc/xdr.c
src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c
src/util/k5test.py
src/util/profile/prof_err.et
src/util/profile/prof_init.c
src/util/profile/prof_parse.c
src/util/profile/prof_test1

index 09825524f528b724732ff93f5dddf7c57e2775d8..9114350619e34409df9d86a4057748b120f769f8 100644 (file)
@@ -40,6 +40,21 @@ foo = baz
 
 then the second value of foo (baz) would never be read.
 
+The @code{krb5.conf} file can include other files using either of the
+following directives at the beginning of a line:
+
+@smallexample
+include @var{FILENAME}
+includedir @var{DIRNAME}
+@end smallexample
+
+@var{FILENAME} or @var{DIRNAME} should be an absolute path.  The named
+file or directory must exist and be readable.  Including a directory
+includes all files within the directory whose names consist solely of
+alphanumeric characters, dashes, or underscores.  Included profile files
+are syntactically independent of their parents, so each included file
+must begin with a section header.
+
 The @code{krb5.conf} file may contain any or all of the following 
 sections:
 
index 35040a1eeb889291cd7a8559ad45cfb8d4146af8..3d342bc3d1ea77a07d138376fcf76af83dd0671e 100644 (file)
@@ -445,6 +445,7 @@ ETOUT =     \
        $(INC)asn1_err.h $(ET)asn1_err.c \
        $(INC)kdb5_err.h $(ET)kdb5_err.c \
        $(INC)krb5_err.h $(ET)krb5_err.c \
+       $(INC)k5e1_err.h $(ET)k5e1_err.c \
        $(INC)kv5m_err.h $(ET)kv5m_err.c \
        $(INC)krb524_err.h $(ET)krb524_err.c \
        $(PR)prof_err.h $(PR)prof_err.c \
@@ -501,6 +502,8 @@ $(INC)kdb5_err.h: $(AH) $(ET)kdb5_err.et
        $(AWK) -f $(AH) outfile=$@ $(ET)kdb5_err.et
 $(INC)krb5_err.h: $(AH) $(ET)krb5_err.et
        $(AWK) -f $(AH) outfile=$@ $(ET)krb5_err.et
+$(INC)k5e1_err.h: $(AH) $(ET)k5e1_err.et
+       $(AWK) -f $(AH) outfile=$@ $(ET)k5e1_err.et
 $(INC)kv5m_err.h: $(AH) $(ET)kv5m_err.et
        $(AWK) -f $(AH) outfile=$@ $(ET)kv5m_err.et
 $(INC)krb524_err.h: $(AH) $(ET)krb524_err.et
@@ -524,6 +527,8 @@ $(ET)kdb5_err.c: $(AC) $(ET)kdb5_err.et
        $(AWK) -f $(AC) outfile=$@ $(ET)kdb5_err.et
 $(ET)krb5_err.c: $(AC) $(ET)krb5_err.et
        $(AWK) -f $(AC) outfile=$@ $(ET)krb5_err.et
+$(ET)k5e1_err.c: $(AC) $(ET)k5e1_err.et
+       $(AWK) -f $(AC) outfile=$@ $(ET)k5e1_err.et
 $(ET)kv5m_err.c: $(AC) $(ET)kv5m_err.et
        $(AWK) -f $(AC) outfile=$@ $(ET)kv5m_err.et
 $(ET)krb524_err.c: $(AC) $(ET)krb524_err.et
@@ -541,8 +546,8 @@ $(CE)test1.c: $(AC) $(CE)test1.et
 $(CE)test2.c: $(AC) $(CE)test2.et
        $(AWK) -f $(AC) outfile=$@ $(CE)test2.et
 
-KRBHDEP = $(INC)krb5\krb5.hin $(INC)krb5_err.h $(INC)kdb5_err.h \
-       $(INC)kv5m_err.h $(INC)krb524_err.h $(INC)asn1_err.h
+KRBHDEP = $(INC)krb5\krb5.hin $(INC)krb5_err.h $(INC)k5e1_err.h \
+       $(INC)kdb5_err.h $(INC)kv5m_err.h $(INC)krb524_err.h $(INC)asn1_err.h
 
 $(INC)krb5\krb5.h: $(KRBHDEP)
        rm -f $@
index 5ecfd426c70ce04837fecd1eee93ed7dcaecf4ae..db3305f5980f9de9855603793accdb8557258426 100644 (file)
@@ -59,6 +59,17 @@ multiple values.  Here is an example of the INI-style format used by
 .fi
 .sp
 
+.PP
+.I krb5.conf
+can include other files using the directives "include FILENAME" or
+"includedir DIRNAME", which must occur at the beginning of a line.
+FILENAME or DIRNAME should be an absolute path.  The named file or
+directory must exist and be readable.  Including a directory includes
+all files within the directory whose names consist solely of
+alphanumeric characters, dashes, or underscores.  Included profile
+files are syntactically independent of their parents, so each included
+file must begin with a section header.
+
 .PP
 The following sections are currently used in the 
 .I krb5.conf
index 1f3564a7c6cda38c04a39c441a564721f06358c3..5c178a24af9cdaf05f2594b849f2f8f606f29bca 100644 (file)
@@ -17,9 +17,10 @@ maybe-make-db.h-sys:
 maybe-make-db.h-redirect:
        test -r db.h || echo '#include <@DB_HEADER@>' > db.h
 
-ET_HEADERS = adm_err.h asn1_err.h kdb5_err.h krb5_err.h
+ET_HEADERS = adm_err.h asn1_err.h kdb5_err.h krb5_err.h k5e1_err.h
 K5_ET_HEADERS = \
        ../lib/krb5/error_tables/krb5_err.h \
+       ../lib/krb5/error_tables/k5e1_err.h \
        ../lib/krb5/error_tables/kdb5_err.h \
        ../lib/krb5/error_tables/kv5m_err.h \
        ../lib/krb5/error_tables/krb524_err.h \
@@ -103,7 +104,7 @@ private-and-public-decls: $(HEADERS_TO_CHECK)
 
 #
 # Build the error table include files:
-# asn1_err.h kdb5_err.h krb5_err.h kv5m_err.h krb524_err.h
+# asn1_err.h kdb5_err.h krb5_err.h k5e1_err.h kv5m_err.h krb524_err.h
 
 $(K5_ET_HEADERS): rebuild-error-tables
        : $@
@@ -114,8 +115,8 @@ rebuild-error-tables:
 force:
 
 clean-unix::
-       $(RM) krb5/krb5.h krb5_err.h kdb5_err.h kv5m_err.h krb524_err.h \
-               asn1_err.h private-and-public-decls krb5.stamp
+       $(RM) krb5/krb5.h krb5_err.h k5e1_err.h kdb5_err.h kv5m_err.h \
+               krb524_err.h asn1_err.h private-and-public-decls krb5.stamp
        $(RM) $(ET_HEADERS) autoconf.stamp
 
 clean-windows::
index 89bc79d077730b30278c88c2c3d3f4187ce272d2..f110179ceca7c8e1bc009e5dc6201ff7ada04899 100644 (file)
@@ -1147,6 +1147,8 @@ typedef struct _krb5_gic_opt_private {
     char * fast_ccache_name;
     krb5_ccache out_ccache;
     krb5_flags fast_flags;
+    krb5_expire_callback_func *expire_cb;
+    void *expire_data;
 } krb5_gic_opt_private;
 
 /*
index 7d7e425a3e2e4f7409c3f6b861d6413cf3ad4da0..f49ef95e208bd81e4f46db5b052ab3074dcfb523 100644 (file)
@@ -1003,6 +1003,8 @@ krb5_verify_checksum(krb5_context context, krb5_cksumtype ctype,
 #define KRB5_LRQ_ONE_LAST_REQ           (-5)
 #define KRB5_LRQ_ALL_PW_EXPTIME         6
 #define KRB5_LRQ_ONE_PW_EXPTIME         (-6)
+#define KRB5_LRQ_ALL_ACCT_EXPTIME       7
+#define KRB5_LRQ_ONE_ACCT_EXPTIME       (-7)
 
 /* PADATA types */
 #define KRB5_PADATA_NONE                0
@@ -2352,6 +2354,48 @@ krb5_get_init_creds_opt_get_fast_flags(krb5_context context,
 /* Fast flags*/
 #define KRB5_FAST_REQUIRED 1l<<0 /*!< Require KDC to support FAST*/
 
+typedef void
+krb5_expire_callback_func(krb5_context context, void *data,
+                          krb5_timestamp password_expiration,
+                          krb5_timestamp account_expiration,
+                          krb5_boolean is_last_req);
+
+/**
+ * Set a callback to receive password and account expiration times.
+ *
+ * This option only applies to krb5_get_init_creds_password().  @a cb will be
+ * invoked if and only if credentials are successfully acquired.  The callback
+ * will receive the @a context from the krb5_get_init_creds_password() call and
+ * the @a data argument supplied with this API.  The remaining arguments should
+ * be interpreted as follows:
+ *
+ * If @a is_last_req is true, then the KDC reply contained last-req entries
+ * which unambiguously indicated the password expiration, account expiration,
+ * or both.  (If either value was not present, the corresponding argument will
+ * be 0.)  Furthermore, a non-zero @a password_expiration should be taken as a
+ * suggestion from the KDC that a warning be displayed.
+ *
+ * If @a is_last_req is false, then @a account_expiration will be 0 and @a
+ * password_expiration will contain the expiration time of either the password
+ * or account, or 0 if no expiration time was indicated in the KDC reply.  The
+ * callback should independently decide whether to display a password
+ * expiration warning.
+ *
+ * Note that @a cb may be invoked even if credentials are being acquired for
+ * the kadmin/changepw service in order to change the password.  It is the
+ * caller's responsibility to avoid displaying a password expiry warning in
+ * this case.
+ *
+ * Setting an expire callback with this API will cause
+ * krb5_get_init_creds_password() not to send password expiry warnings to the
+ * prompter, as it ordinarily may.
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_get_init_creds_opt_set_expire_callback(krb5_context context,
+                                            krb5_get_init_creds_opt *opt,
+                                            krb5_expire_callback_func cb,
+                                            void *data);
+
 krb5_error_code KRB5_CALLCONV
 krb5_get_init_creds_password(krb5_context context, krb5_creds *creds,
                              krb5_principal client, char *password,
index 48a55e4c71176bef513e0af00092084622912a4c..fa98ae3a0d2bbfa1f223eecd357d32cbabee673d 100644 (file)
@@ -88,6 +88,17 @@ prepare_error_as(struct kdc_request_state *, krb5_kdc_req *,
                  int, krb5_data *, krb5_principal, krb5_data **,
                  const char *);
 
+/* Determine the key-expiration value according to RFC 4120 section 5.4.2. */
+static krb5_timestamp
+get_key_exp(krb5_db_entry *entry)
+{
+    if (entry->expiration == 0)
+        return entry->pw_expiration;
+    if (entry->pw_expiration == 0)
+        return entry->expiration;
+    return min(entry->expiration, entry->pw_expiration);
+}
+
 /*ARGSUSED*/
 krb5_error_code
 process_as_req(krb5_kdc_req *request, krb5_data *req_pkt,
@@ -541,7 +552,7 @@ process_as_req(krb5_kdc_req *request, krb5_data *req_pkt,
         goto errout;
     }
     reply_encpart.nonce = request->nonce;
-    reply_encpart.key_exp = client->expiration;
+    reply_encpart.key_exp = get_key_exp(client);
     reply_encpart.flags = enc_tkt_reply.flags;
     reply_encpart.server = ticket_reply.server;
 
diff --git a/src/lib/gssapi/generic/util_canonhost.c b/src/lib/gssapi/generic/util_canonhost.c
deleted file mode 100644 (file)
index e27d656..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
- * Copyright 1993 by OpenVision Technologies, Inc.
- *
- * Permission to use, copy, modify, distribute, and sell this software
- * and its documentation for any purpose is hereby granted without fee,
- * provided that the above copyright notice appears in all copies and
- * that both that copyright notice and this permission notice appear in
- * supporting documentation, and that the name of OpenVision not be used
- * in advertising or publicity pertaining to distribution of the software
- * without specific, written prior permission. OpenVision makes no
- * representations about the suitability of this software for any
- * purpose.  It is provided "as is" without express or implied warranty.
- *
- * OPENVISION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
- * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
- * EVENT SHALL OPENVISION BE LIABLE FOR ANY SPECIAL, INDIRECT OR
- * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
- * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
- * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
- * PERFORMANCE OF THIS SOFTWARE.
- */
-
-/*
- * $Id$
- */
-
-/* This file could be OS specific */
-
-#include "gssapiP_generic.h"
-
-#include "port-sockets.h"
-
-#ifdef HAVE_SYS_TYPES_H
-#include <sys/types.h>
-#endif
-#include <ctype.h>
-#include <string.h>
-
-char *
-g_canonicalize_host(char *hostname)
-{
-    struct hostent *hent;
-    char *haddr;
-    char *canon, *str;
-
-    if ((hent = gethostbyname(hostname)) == NULL)
-        return(NULL);
-
-    if (! (haddr = (char *) xmalloc(hent->h_length))) {
-        return(NULL);
-    }
-
-    memcpy(haddr, hent->h_addr_list[0], hent->h_length);
-
-    if (! (hent = gethostbyaddr(haddr, hent->h_length, hent->h_addrtype))) {
-        return(NULL);
-    }
-
-    xfree(haddr);
-
-    if ((canon = (char *) strdup(hent->h_name)) == NULL)
-        return(NULL);
-
-    for (str = canon; *str; str++)
-        if (isupper(*str)) *str = tolower(*str);
-
-    return(canon);
-}
diff --git a/src/lib/gssapi/generic/util_localhost.c b/src/lib/gssapi/generic/util_localhost.c
deleted file mode 100644 (file)
index 7956a07..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
- * Copyright 1993 by OpenVision Technologies, Inc.
- *
- * Permission to use, copy, modify, distribute, and sell this software
- * and its documentation for any purpose is hereby granted without fee,
- * provided that the above copyright notice appears in all copies and
- * that both that copyright notice and this permission notice appear in
- * supporting documentation, and that the name of OpenVision not be used
- * in advertising or publicity pertaining to distribution of the software
- * without specific, written prior permission. OpenVision makes no
- * representations about the suitability of this software for any
- * purpose.  It is provided "as is" without express or implied warranty.
- *
- * OPENVISION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
- * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
- * EVENT SHALL OPENVISION BE LIABLE FOR ANY SPECIAL, INDIRECT OR
- * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
- * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
- * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
- * PERFORMANCE OF THIS SOFTWARE.
- */
-
-/*
- * $Id$
- */
-
-/* This file could be OS specific */
-
-#include <sys/param.h>
-
-#include "gssapiP_generic.h"
-
-#ifndef MAXHOSTNAMELEN
-#define MAXHOSTNAMELEN 64
-#endif
-
-char *
-g_local_host_name(void)
-{
-    char buf[MAXHOSTNAMELEN+1], *ptr;
-
-    if (gethostname(buf, sizeof(buf)) < 0)
-        return 0;
-
-    buf[sizeof(buf)-1] = '\0';
-
-    return strdup(buf);
-}
index 28e25052abd7547112282b89381f0755126f5c13..bceab6173f0b024294d7e62fb2beb2f93d806628 100644 (file)
@@ -103,19 +103,18 @@ gss_krb5int_register_acceptor_identity(OM_uint32 *minor_status,
                                        const gss_OID desired_object,
                                        gss_buffer_t value)
 {
-    char *new, *old;
+    char *new = NULL, *old;
     int err;
 
     err = gss_krb5int_initialize_library();
     if (err != 0)
         return GSS_S_FAILURE;
 
-    if (value->value == NULL)
-        return GSS_S_FAILURE;
-
-    new = strdup((char *)value->value);
-    if (new == NULL)
-        return GSS_S_FAILURE;
+    if (value->value != NULL) {
+        new = strdup((char *)value->value);
+        if (new == NULL)
+            return GSS_S_FAILURE;
+    }
 
     err = k5_mutex_lock(&gssint_krb5_keytab_lock);
     if (err) {
@@ -125,8 +124,7 @@ gss_krb5int_register_acceptor_identity(OM_uint32 *minor_status,
     old = krb5_gss_keytab;
     krb5_gss_keytab = new;
     k5_mutex_unlock(&gssint_krb5_keytab_lock);
-    if (old != NULL)
-        free(old);
+    free(old);
     return GSS_S_COMPLETE;
 }
 
index 0d87f90c96d2b2cd52c0d4c286d0ffc0c964f87a..d2a47acb8407b21db430696da932d0610e1cd0be 100644 (file)
@@ -253,7 +253,7 @@ krb5_gss_register_acceptor_identity(const char *keytab)
     OM_uint32 minor_status;
     gss_buffer_desc req_buffer;
 
-    req_buffer.length = strlen(keytab);
+    req_buffer.length = (keytab == NULL) ? 0 : strlen(keytab);
     req_buffer.value = (char *)keytab;
 
     major_status = gssspi_mech_invoke(&minor_status,
index 36724eab3dbaa76320b7c966036ca486b8ea5ba6..5fb67ebb1c3d7c4ffdd55b2e75dff6eb47945855 100644 (file)
@@ -1021,7 +1021,7 @@ xdr_krb5_enctype(XDR *xdrs, krb5_enctype *objp)
     * is safe.
     */
 
-   if (!xdr_u_int(xdrs, (unsigned int *) objp))
+   if (!xdr_int32(xdrs, (int32_t *) objp))
        return (FALSE);
    return (TRUE);
 }
index 497236326ad48875b461af42990f2f636c5aa5d3..d340b7b150ee1c5f44d796d2f6e70eabc3003fc6 100644 (file)
@@ -10,14 +10,15 @@ THDRDIR=$(BUILDTOP)$(S)include
 EHDRDIR=$(BUILDTOP)$(S)include
 ETDIR=$(top_srcdir)$(S)util$(S)et
 
-STLIBOBJS= asn1_err.o kdb5_err.o krb5_err.o \
+STLIBOBJS= asn1_err.o kdb5_err.o krb5_err.o k5e1_err.o \
       kv5m_err.o krb524_err.o
 
-HDRS= asn1_err.h kdb5_err.h krb5_err.h kv5m_err.h krb524_err.h
-OBJS= $(OUTPRE)asn1_err.$(OBJEXT) $(OUTPRE)kdb5_err.$(OBJEXT) $(OUTPRE)krb5_err.$(OBJEXT) \
+HDRS= asn1_err.h kdb5_err.h krb5_err.h k5e1_err.h kv5m_err.h krb524_err.h
+OBJS= $(OUTPRE)asn1_err.$(OBJEXT) $(OUTPRE)kdb5_err.$(OBJEXT) \
+      $(OUTPRE)krb5_err.$(OBJEXT) $(OUTPRE)k5e1_err.$(OBJEXT) \
       $(OUTPRE)kv5m_err.$(OBJEXT) $(OUTPRE)krb524_err.$(OBJEXT)
-ETSRCS= asn1_err.c kdb5_err.c krb5_err.c kv5m_err.c krb524_err.c
-SRCS= asn1_err.c kdb5_err.c krb5_err.c kv5m_err.c krb524_err.c
+ETSRCS= asn1_err.c kdb5_err.c krb5_err.c k5e1_err.c kv5m_err.c krb524_err.c
+SRCS= asn1_err.c kdb5_err.c krb5_err.c k5e1_err.c kv5m_err.c krb524_err.c
 
 ##DOS##LIBOBJS = $(OBJS)
 
@@ -30,16 +31,19 @@ awk-windows:
        $(AWK) -f $(ETDIR)/et_h.awk outfile=asn1_err.h asn1_err.et
        $(AWK) -f $(ETDIR)/et_h.awk outfile=kdb5_err.h kdb5_err.et
        $(AWK) -f $(ETDIR)/et_h.awk outfile=krb5_err.h krb5_err.et
+       $(AWK) -f $(ETDIR)/et_h.awk outfile=k5e1_err.h k5e1_err.et
        $(AWK) -f $(ETDIR)/et_h.awk outfile=kv5m_err.h kv5m_err.et
        $(AWK) -f $(ETDIR)/et_h.awk outfile=krb524_err.h krb524_err.et
        $(AWK) -f $(ETDIR)/et_c.awk outfile=asn1_err.c asn1_err.et
        $(AWK) -f $(ETDIR)/et_c.awk outfile=kdb5_err.c kdb5_err.et
        $(AWK) -f $(ETDIR)/et_c.awk outfile=krb5_err.c krb5_err.et
+       $(AWK) -f $(ETDIR)/et_c.awk outfile=k5e1_err.c k5e1_err.et
        $(AWK) -f $(ETDIR)/et_c.awk outfile=kv5m_err.c kv5m_err.et
        $(AWK) -f $(ETDIR)/et_c.awk outfile=krb524_err.c krb524_err.et
        if exist asn1_err.h copy asn1_err.h "$(EHDRDIR)"
        if exist kdb5_err.h copy kdb5_err.h "$(EHDRDIR)"
        if exist krb5_err.h copy krb5_err.h "$(EHDRDIR)"
+       if exist k5e1_err.h copy k5e1_err.h "$(EHDRDIR)"
        if exist kv5m_err.h copy kv5m_err.h "$(EHDRDIR)"
        if exist krb524_err.h copy krb524_err.h "$(EHDRDIR)"
 
@@ -49,6 +53,7 @@ awk-windows:
 $(OUTPRE)asn1_err.$(OBJEXT): asn1_err.c
 $(OUTPRE)kdb5_err.$(OBJEXT): kdb5_err.c
 $(OUTPRE)krb5_err.$(OBJEXT): krb5_err.c
+$(OUTPRE)k5e1_err.$(OBJEXT): k5e1_err.c
 $(OUTPRE)kv5m_err.$(OBJEXT): kv5m_err.c
 $(OUTPRE)krb524_err.$(OBJEXT): krb524_err.c
 
diff --git a/src/lib/krb5/error_tables/k5e1_err.et b/src/lib/krb5/error_tables/k5e1_err.et
new file mode 100644 (file)
index 0000000..6d56210
--- /dev/null
@@ -0,0 +1,33 @@
+#
+# lib/krb5/error_tables/k5e1_err.et
+#
+# Copyright 2010 by the Massachusetts Institute of Technology.
+# All Rights Reserved.
+#
+# Export of this software from the United States of America may
+#   require a specific license from the United States Government.
+#   It is the responsibility of any person or organization contemplating
+#   export to obtain such a license before exporting.
+# 
+# WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+# distribute this software and its documentation for any purpose and
+# without fee is hereby granted, provided that the above copyright
+# notice appear in all copies and that both that copyright notice and
+# this permission notice appear in supporting documentation, and that
+# the name of M.I.T. not be used in advertising or publicity pertaining
+# to distribution of the software without specific, written prior
+# permission.  Furthermore if you modify this software you must label
+# your software as modified software and not distribute it in such a
+# fashion that it might be confused with the original M.I.T. software.
+# M.I.T. makes no representations about the suitability of
+# this software for any purpose.  It is provided "as is" without express
+# or implied warranty.
+# 
+#
+# The Kerberos v5 library error code expansion table (#1).
+# This table exists to hold new libkrb5 error codes since the
+# original krb5 error table is full.
+#
+error_table k5e1
+
+end
index 0c013862d539fb890a20b0a1ef01842dbc0dc2de..1b4d879677e494cd1dc98af4dd12c12f08c8e6db 100644 (file)
@@ -386,6 +386,9 @@ t_princ: $(T_PRINC_OBJS) $(KRB5_BASE_DEPLIBS)
 t_etypes: $(T_ETYPES_OBJS) $(KRB5_BASE_DEPLIBS)
        $(CC_LINK) -o t_etypes $(T_ETYPES_OBJS) $(KRB5_BASE_LIBS)
 
+t_expire_warn: t_expire_warn.o $(KRB5_BASE_DEPLIBS)
+       $(CC_LINK) -o $@ t_expire_warn.o $(KRB5_BASE_LIBS)
+
 TEST_PROGS= t_walk_rtree t_kerb t_ser t_deltat t_expand t_authdata t_pac \
        t_princ t_etypes
 
@@ -426,6 +429,9 @@ check-unix:: $(TEST_PROGS)
        $(RUN_SETUP) $(VALGRIND) ./t_princ
        $(RUN_SETUP) $(VALGRIND) ./t_etypes
 
+check-pytests:: t_expire_warn
+       $(RUNPYTEST) $(srcdir)/t_expire_warn.py $(PYTESTFLAGS)
+
 clean::
        $(RM) $(OUTPRE)t_walk_rtree$(EXEEXT) $(OUTPRE)t_walk_rtree.$(OBJEXT) \
                $(OUTPRE)t_kerb$(EXEEXT) $(OUTPRE)t_kerb.$(OBJEXT)      \
index ab29740bb6352e06bd63bada8ac3e56d41bb6043..36f4f00a1f3ff9ea4cd900c573be200685323619 100644 (file)
@@ -480,3 +480,22 @@ krb5_get_init_creds_opt_get_fast_flags(krb5_context context,
     *out_flags = opte->opt_private->fast_flags;
     return retval;
 }
+
+krb5_error_code KRB5_CALLCONV
+krb5_get_init_creds_opt_set_expire_callback(krb5_context context,
+                                            krb5_get_init_creds_opt *opt,
+                                            krb5_expire_callback_func cb,
+                                            void *data)
+{
+    krb5_error_code retval = 0;
+    krb5_gic_opt_ext *opte;
+
+    retval = krb5int_gic_opt_to_opte(context, opt, &opte, 0,
+                                     "krb5_get_init_creds_opt_set_"
+                                     "expire_callback");
+    if (retval)
+        return retval;
+    opte->opt_private->expire_cb = cb;
+    opte->opt_private->expire_data = data;
+    return retval;
+}
index 6df9014986728eb8ddff575bf3268d40000b9a3d..1e0b741e38ac141395996a1832b492ad89284db7 100644 (file)
@@ -56,12 +56,10 @@ krb5_get_as_key_password(krb5_context context,
 
         /* PROMPTER_INVOCATION */
         krb5int_set_prompt_types(context, &prompt_type);
-        if ((ret = (((*prompter)(context, prompter_data, NULL, NULL,
-                                 1, &prompt))))) {
-            krb5int_set_prompt_types(context, 0);
-            return(ret);
-        }
+        ret = (*prompter)(context, prompter_data, NULL, NULL, 1, &prompt);
         krb5int_set_prompt_types(context, 0);
+        if (ret)
+            return(ret);
     }
 
     if ((salt->length == -1 || salt->length == SALT_TYPE_AFS_LENGTH) && (salt->data == NULL)) {
@@ -106,6 +104,111 @@ krb5_init_creds_set_password(krb5_context context,
     return 0;
 }
 
+/* Return the password expiry time indicated by enc_part2.  Set *is_last_req
+ * if the information came from a last_req value. */
+static void
+get_expiry_times(krb5_enc_kdc_rep_part *enc_part2, krb5_timestamp *pw_exp,
+                 krb5_timestamp *acct_exp, krb5_boolean *is_last_req)
+{
+    krb5_last_req_entry **last_req;
+    krb5_int32 lr_type;
+
+    *pw_exp = 0;
+    *acct_exp = 0;
+    *is_last_req = FALSE;
+
+    /* Look for last-req entries for password or account expiration. */
+    if (enc_part2->last_req) {
+        for (last_req = enc_part2->last_req; *last_req; last_req++) {
+            lr_type = (*last_req)->lr_type;
+            if (lr_type == KRB5_LRQ_ALL_PW_EXPTIME ||
+                lr_type == KRB5_LRQ_ONE_PW_EXPTIME) {
+                *is_last_req = TRUE;
+                *pw_exp = (*last_req)->value;
+            } else if (lr_type == KRB5_LRQ_ALL_ACCT_EXPTIME ||
+                       lr_type == KRB5_LRQ_ONE_ACCT_EXPTIME) {
+                *is_last_req = TRUE;
+                *acct_exp = (*last_req)->value;
+            }
+        }
+    }
+
+    /* If we didn't find any, use the ambiguous key_exp field. */
+    if (*is_last_req == FALSE)
+        *pw_exp = enc_part2->key_exp;
+}
+
+/*
+ * Send an appropriate warning prompter if as_reply indicates that the password
+ * is going to expire soon.  If an expire callback was provided, use that
+ * instead.
+ */
+static void
+warn_pw_expiry(krb5_context context, krb5_get_init_creds_opt *options,
+               krb5_prompter_fct prompter, void *data,
+               const char *in_tkt_service, krb5_kdc_rep *as_reply)
+{
+    krb5_error_code ret;
+    krb5_timestamp pw_exp, acct_exp, now;
+    krb5_boolean is_last_req;
+    krb5_deltat delta;
+    krb5_gic_opt_ext *opte;
+    char ts[256], banner[1024];
+
+    get_expiry_times(as_reply->enc_part2, &pw_exp, &acct_exp, &is_last_req);
+
+    ret = krb5int_gic_opt_to_opte(context, options, &opte, 0, "");
+    if (ret == 0 && opte->opt_private->expire_cb != NULL) {
+        krb5_expire_callback_func *cb = opte->opt_private->expire_cb;
+        void *cb_data = opte->opt_private->expire_data;
+
+        /* Invoke the expire callback and don't send prompter warnings. */
+        (*cb)(context, cb_data, pw_exp, acct_exp, is_last_req);
+        return;
+    }
+
+    /* Don't warn if the password is being changed. */
+    if (in_tkt_service && strcmp(in_tkt_service, "kadmin/changepw") == 0)
+        return;
+
+    /*
+     * If the expiry time came from a last_req field, assume the KDC wants us
+     * to warn.  Otherwise, warn only if the expiry time is less than a week
+     * from now.
+     */
+    ret = krb5_timeofday(context, &now);
+    if (ret != 0)
+        return;
+    if (!is_last_req &&
+        (pw_exp < now || (pw_exp - now) > 7 * 24 * 60 * 60))
+        return;
+
+    if (!prompter)
+        return;
+
+    ret = krb5_timestamp_to_string(pw_exp, ts, sizeof(ts));
+    if (ret != 0)
+        return;
+
+    delta = pw_exp - now;
+    if (delta < 3600) {
+        snprintf(banner, sizeof(banner),
+                 "Warning: Your password will expire in less than one hour "
+                 "on %s", ts);
+    } else if (delta < 86400*2) {
+        snprintf(banner, sizeof(banner),
+                 "Warning: Your password will expire in %d hour%s on %s",
+                 delta / 3600, delta < 7200 ? "" : "s", ts);
+    } else {
+        snprintf(banner, sizeof(banner),
+                 "Warning: Your password will expire in %d days on %s",
+                 delta / 86400, ts);
+    }
+
+    /* PROMPTER_INVOCATION */
+    (*prompter)(context, data, 0, banner, 0, 0);
+}
+
 krb5_error_code KRB5_CALLCONV
 krb5_get_init_creds_password(krb5_context context,
                              krb5_creds *creds,
@@ -263,11 +366,11 @@ krb5_get_init_creds_password(krb5_context context,
 
         /* PROMPTER_INVOCATION */
         krb5int_set_prompt_types(context, prompt_types);
-        if ((ret = ((*prompter)(context, data, 0, banner,
-                                sizeof(prompt)/sizeof(prompt[0]), prompt))))
-            goto cleanup;
+        ret = (*prompter)(context, data, 0, banner,
+                          sizeof(prompt)/sizeof(prompt[0]), prompt);
         krb5int_set_prompt_types(context, 0);
-
+        if (ret)
+            goto cleanup;
 
         if (strcmp(pw0.data, pw1.data) != 0) {
             ret = KRB5_LIBOS_BADPWDMATCH;
@@ -334,83 +437,13 @@ krb5_get_init_creds_password(krb5_context context,
                                  start_time, in_tkt_service, options,
                                  krb5_get_as_key_password, (void *) &pw0,
                                  &use_master, &as_reply);
+    if (ret)
+        goto cleanup;
 
 cleanup:
-    krb5int_set_prompt_types(context, 0);
-    /* if getting the password was successful, then check to see if the
-       password is about to expire, and warn if so */
-
-    if (ret == 0) {
-        krb5_timestamp now;
-        krb5_last_req_entry **last_req;
-        int hours;
-
-        /* XXX 7 days should be configurable.  This is all pretty ad hoc,
-           and could probably be improved if I was willing to screw around
-           with timezones, etc. */
-
-        if (prompter &&
-            (!in_tkt_service ||
-             (strcmp(in_tkt_service, "kadmin/changepw") != 0)) &&
-            ((ret = krb5_timeofday(context, &now)) == 0) &&
-            as_reply->enc_part2->key_exp &&
-            ((hours = ((as_reply->enc_part2->key_exp-now)/(60*60))) <= 7*24) &&
-            (hours >= 0)) {
-            if (hours < 1)
-                snprintf(banner, sizeof(banner),
-                         "Warning: Your password will expire in less than one hour.");
-            else if (hours <= 48)
-                snprintf(banner, sizeof(banner),
-                         "Warning: Your password will expire in %d hour%s.",
-                         hours, (hours == 1)?"":"s");
-            else
-                snprintf(banner, sizeof(banner),
-                         "Warning: Your password will expire in %d days.",
-                         hours/24);
-
-            /* ignore an error here */
-            /* PROMPTER_INVOCATION */
-            (*prompter)(context, data, 0, banner, 0, 0);
-        } else if (prompter &&
-                   (!in_tkt_service ||
-                    (strcmp(in_tkt_service, "kadmin/changepw") != 0)) &&
-                   as_reply->enc_part2 && as_reply->enc_part2->last_req) {
-            /*
-             * Check the last_req fields
-             */
-
-            for (last_req = as_reply->enc_part2->last_req; *last_req; last_req++)
-                if ((*last_req)->lr_type == KRB5_LRQ_ALL_PW_EXPTIME ||
-                    (*last_req)->lr_type == KRB5_LRQ_ONE_PW_EXPTIME) {
-                    krb5_deltat delta;
-                    char ts[256];
-
-                    if ((ret = krb5_timeofday(context, &now)))
-                        break;
-
-                    if ((ret = krb5_timestamp_to_string((*last_req)->value,
-                                                        ts, sizeof(ts))))
-                        break;
-
-                    delta = (*last_req)->value - now;
-                    if (delta < 3600)
-                        snprintf(banner, sizeof(banner),
-                                 "Warning: Your password will expire in less than one hour on %s",
-                                 ts);
-                    else if (delta < 86400*2)
-                        snprintf(banner, sizeof(banner),
-                                 "Warning: Your password will expire in %d hour%s on %s",
-                                 delta / 3600, delta < 7200 ? "" : "s", ts);
-                    else
-                        snprintf(banner, sizeof(banner),
-                                 "Warning: Your password will expire in %d days on %s",
-                                 delta / 86400, ts);
-                    /* ignore an error here */
-                    /* PROMPTER_INVOCATION */
-                    (*prompter)(context, data, 0, banner, 0, 0);
-                }
-        }
-    }
+    if (ret == 0)
+        warn_pw_expiry(context, options, prompter, data, in_tkt_service,
+                       as_reply);
 
     if (chpw_opts)
         krb5_get_init_creds_opt_free(context, chpw_opts);
index 014002981b9f7ee8c69781e45b385dfff1cc9260..9bc7c42f902f7df510a4264430bbba1dbd9c1ba1 100644 (file)
  *
  *  server specifies the expected server's name for the ticket; if NULL, then
  *  any server will be accepted if the key can be found, and the caller should
- *  verify that the principal is something it trusts.
+ *  verify that the principal is something it trusts. With the exception of the
+ *  kdb keytab, the ticket's server field need not match the name passed in for
+ *  server. All that is required is that the ticket be encrypted with a key
+ *  from the keytab associated with the specified server principal. This
+ *  permits the KDC to have a set of aliases for the server without keeping
+ *  this information consistent with the server. So, when server is non-null,
+ *  the principal expected by the application needs to be consistent with the
+ *  local keytab, but not with the informational name in the ticket.
  *
  *  rcache specifies a replay detection cache used to store authenticators and
  *  server names
diff --git a/src/lib/krb5/krb/t_expire_warn.c b/src/lib/krb5/krb/t_expire_warn.c
new file mode 100644 (file)
index 0000000..6e8d87c
--- /dev/null
@@ -0,0 +1,90 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ * lib/krb5/krb/t_expire_warn.c
+ *
+ * Copyright (C) 2010 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Export of this software from the United States of America may
+ *   require a specific license from the United States Government.
+ *   It is the responsibility of any person or organization contemplating
+ *   export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission.  Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose.  It is provided "as is" without express
+ * or implied warranty.
+ *
+ *
+ * Test harness for password expiration warnings.
+ */
+
+#include "k5-int.h"
+
+static int exp_dummy, prompt_dummy;
+
+static krb5_error_code
+prompter_cb(krb5_context ctx, void *data, const char *name,
+           const char *banner, int num_prompts, krb5_prompt prompts[])
+{
+    /* Not expecting any actual prompts, only banners. */
+    assert(num_prompts == 0);
+    assert(banner != NULL);
+    printf("Prompter: %s\n", banner);
+    return 0;
+}
+
+static void
+expire_cb(krb5_context ctx, void *data, krb5_timestamp password_expiration,
+         krb5_timestamp account_expiration, krb5_boolean is_last_req)
+{
+    printf("password_expiration = %ld\n", (long)password_expiration);
+    printf("account_expiration = %ld\n", (long)account_expiration);
+    printf("is_last_req = %d\n", (int)is_last_req);
+}
+
+int
+main(int argc, char **argv)
+{
+    krb5_context ctx;
+    krb5_get_init_creds_opt *opt;
+    char *user, *password, *service = NULL;
+    krb5_boolean use_cb;
+    krb5_principal client;
+    krb5_creds creds;
+
+    if (argc < 4) {
+       fprintf(stderr, "Usage: %s username password {1|0} [service]\n",
+               argv[0]);
+       return 1;
+    }
+    user = argv[1];
+    password = argv[2];
+    use_cb = atoi(argv[3]);
+    if (argc >= 5)
+       service = argv[4];
+
+    assert(krb5_init_context(&ctx) == 0);
+    assert(krb5_get_init_creds_opt_alloc(ctx, &opt) == 0);
+    if (use_cb) {
+       assert(krb5_get_init_creds_opt_set_expire_callback(ctx, opt, expire_cb,
+                                                          &exp_dummy) == 0);
+    }
+    assert(krb5_parse_name(ctx, user, &client) == 0);
+    assert(krb5_get_init_creds_password(ctx, &creds, client, password,
+                                       prompter_cb, &prompt_dummy, 0, service,
+                                       opt) == 0);
+    krb5_get_init_creds_opt_free(ctx, opt);
+    krb5_free_principal(ctx, client);
+    krb5_free_cred_contents(ctx, &creds);
+    return 0;
+}
diff --git a/src/lib/krb5/krb/t_expire_warn.py b/src/lib/krb5/krb/t_expire_warn.py
new file mode 100644 (file)
index 0000000..dc49a4c
--- /dev/null
@@ -0,0 +1,62 @@
+# Copyright (C) 2010 by the Massachusetts Institute of Technology.
+# All rights reserved.
+#
+# Export of this software from the United States of America may
+#   require a specific license from the United States Government.
+#   It is the responsibility of any person or organization contemplating
+#   export to obtain such a license before exporting.
+#
+# WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+# distribute this software and its documentation for any purpose and
+# without fee is hereby granted, provided that the above copyright
+# notice appear in all copies and that both that copyright notice and
+# this permission notice appear in supporting documentation, and that
+# the name of M.I.T. not be used in advertising or publicity pertaining
+# to distribution of the software without specific, written prior
+# permission.  Furthermore if you modify this software you must label
+# your software as modified software and not distribute it in such a
+# fashion that it might be confused with the original M.I.T. software.
+# M.I.T. makes no representations about the suitability of
+# this software for any purpose.  It is provided "as is" without express
+# or implied warranty.
+
+#!/usr/bin/python
+from k5test import *
+
+# Create a bare-bones KDC.
+realm = K5Realm(create_user=False, create_host=False, start_kadmind=False)
+
+# Create principals with various password expirations.
+realm.run_kadminl('addprinc -pw pass noexpire')
+realm.run_kadminl('addprinc -pw pass -pwexpire "30 minutes" minutes')
+realm.run_kadminl('addprinc -pw pass -pwexpire "12 hours" hours')
+realm.run_kadminl('addprinc -pw pass -pwexpire "3 days" days')
+
+# Check for expected prompter warnings when no expire callback is used.
+output = realm.run_as_client(['./t_expire_warn', 'noexpire', 'pass', '0'])
+if output:
+    fail('Unexpected output for noexpire')
+output = realm.run_as_client(['./t_expire_warn', 'minutes', 'pass', '0'])
+if ' less than one hour on ' not in output:
+    fail('Expected warning not seen for minutes')
+output = realm.run_as_client(['./t_expire_warn', 'hours', 'pass', '0'])
+if ' hours on ' not in output:
+    fail('Expected warning not seen for hours')
+output = realm.run_as_client(['./t_expire_warn', 'days', 'pass', '0'])
+if ' days on ' not in output:
+    fail('Expected warning not seen for days')
+
+# Check for expected expire callback behavior.  These tests are
+# carefully agnostic about whether the KDC supports last_req fields,
+# and could be made more specific if last_req support is added.
+output = realm.run_as_client(['./t_expire_warn', 'noexpire', 'pass', '1'])
+if 'password_expiration = 0\n' not in output or \
+        'account_expiration = 0\n' not in output or \
+        'is_last_req = ' not in output:
+    fail('Expected callback output not seen for noexpire')
+output = realm.run_as_client(['./t_expire_warn', 'days', 'pass', '1'])
+if 'password_expiration = ' not in output or \
+        'password_expiration = 0\n' in output:
+    fail('Expected non-zero password expiration not seen for days')
+
+success('Password expiration warning tests.')
index b375c1be06e19cf8a898201d0da6b6bf36470772..57e5c45de2769d09f389c3b1b3ab9017a4ff653a 100644 (file)
@@ -350,6 +350,7 @@ krb5_get_init_creds_opt_set_anonymous
 krb5_get_init_creds_opt_set_canonicalize
 krb5_get_init_creds_opt_set_change_password_prompt
 krb5_get_init_creds_opt_set_etype_list
+krb5_get_init_creds_opt_set_expire_callback
 krb5_get_init_creds_opt_set_fast_ccache_name
 krb5_get_init_creds_opt_set_fast_flags
 krb5_get_init_creds_opt_set_forwardable
index ff67e90f6d021e7811bb62b617d2f1cd185c9e48..8689ee31661b3cdda5496ba9cb82acb3eeb0cf4f 100644 (file)
@@ -145,7 +145,7 @@ xdr_u_int(XDR *xdrs, u_int *up)
                if (!XDR_GETLONG(xdrs, (long *) &l))
                        return (FALSE);
 
-               if (l > UINT_MAX)
+               if ((uint32_t)l > UINT_MAX)
                        return (FALSE);
 
                *up = (u_int) l;
index 1f3e60e926e475975a88c56a4327bf6a7f2ad230..e9d50196dec46a694578da85e8fa862bb182e504 100644 (file)
@@ -2087,7 +2087,7 @@ populate_krb5_db_entry(krb5_context context, krb5_ldap_context *ldap_context,
             goto cleanup;
 
         if (attr_present == TRUE) {
-            if ((mask & KDB_PRINC_EXPIRE_TIME_ATTR) == 1) {
+            if (mask & KDB_PRINC_EXPIRE_TIME_ATTR) {
                 if (expiretime < entry->expiration)
                     entry->expiration = expiretime;
             } else {
@@ -2127,7 +2127,7 @@ populate_krb5_db_entry(krb5_context context, krb5_ldap_context *ldap_context,
             if ((st=krb5_dbe_lookup_last_pwd_change(context, entry, &last_pw_changed)) != 0)
                 goto cleanup;
 
-            if ((mask & KDB_PWD_EXPIRE_TIME_ATTR) == 1) {
+            if (mask & KDB_PWD_EXPIRE_TIME_ATTR) {
                 if ((last_pw_changed + pw_max_life) < entry->pw_expiration)
                     entry->pw_expiration = last_pw_changed + pw_max_life;
             } else
index e506afa7c9e3b230007676aa69206135a2633f27..d88086274bf4aa3dfae6d8c022e9cb746271acff 100644 (file)
@@ -119,7 +119,7 @@ keyword arguments:
 
 * start_kdc=False: Don't start the KDC.  Implies get_creds=False.
 
-* start_kadmin=False: Don't start kadmind.
+* start_kadmind=False: Don't start kadmind.
 
 * get_creds=False: Don't get user credentials.
 
index af7801ee0a804eb3128abd01334f412a6dc453a4..2384127af95dfe46362218f8b1463d42bc26259f 100644 (file)
@@ -60,7 +60,13 @@ error_code   PROF_EXISTS,            "Section already exists"
 error_code     PROF_BAD_BOOLEAN,               "Invalid boolean value"
 error_code     PROF_BAD_INTEGER,               "Invalid integer value"
 
+#
+# new error codes added at end to avoid changing values
+#
 error_code     PROF_MAGIC_FILE_DATA, "Bad magic value in profile_file_data_t"
-
+error_code     PROF_FAIL_INCLUDE_FILE,
+       "Included profile file could not be read"
+error_code     PROF_FAIL_INCLUDE_DIR,
+       "Included profile directory could not be read"
 
 end
index bd42b138054c570a4fef3a4f62abb4c59a171697..408549dca07240a8f8e67e5489c255661ef39b4b 100644 (file)
@@ -27,7 +27,7 @@ profile_init(const_profile_filespec_t *files, profile_t *ret_profile)
     const_profile_filespec_t *fs;
     profile_t profile;
     prf_file_t  new_file, last = 0;
-    errcode_t retval = 0;
+    errcode_t retval = 0, access_retval = 0;
 
     profile = malloc(sizeof(struct _profile_t));
     if (!profile)
@@ -43,7 +43,12 @@ profile_init(const_profile_filespec_t *files, profile_t *ret_profile)
         for (fs = files; !PROFILE_LAST_FILESPEC(*fs); fs++) {
             retval = profile_open_file(*fs, &new_file);
             /* if this file is missing, skip to the next */
-            if (retval == ENOENT || retval == EACCES || retval == EPERM) {
+            if (retval == ENOENT) {
+                continue;
+            }
+            /* If we can't read this file, remember it but keep going. */
+            if (retval == EACCES || retval == EPERM) {
+                access_retval = retval;
                 continue;
             }
             if (retval) {
@@ -58,11 +63,11 @@ profile_init(const_profile_filespec_t *files, profile_t *ret_profile)
         }
         /*
          * If last is still null after the loop, then all the files were
-         * missing, so return the appropriate error.
+         * missing or unreadable, so return the appropriate error.
          */
         if (!last) {
             profile_release(profile);
-            return ENOENT;
+            return access_retval ? access_retval : ENOENT;
         }
     }
 
index 413c7dfbb0b5b6983991fa2502db75f97e41cbe0..7f3d4c9d490dd1a3316d4191b6bdfe879c2918d7 100644 (file)
@@ -1,6 +1,7 @@
 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
 #include "prof_int.h"
 
+#include <sys/types.h>
 #include <stdio.h>
 #include <string.h>
 #ifdef HAVE_STDLIB_H
@@ -8,6 +9,7 @@
 #endif
 #include <errno.h>
 #include <ctype.h>
+#include <dirent.h>
 
 #define SECTION_SEP_CHAR '/'
 
@@ -22,6 +24,8 @@ struct parse_state {
     struct profile_node *current_section;
 };
 
+static errcode_t parse_file(FILE *f, struct parse_state *state);
+
 static char *skip_over_blanks(char *cp)
 {
     while (*cp && isspace((int) (*cp)))
@@ -33,7 +37,7 @@ static void strip_line(char *line)
 {
     char *p = line + strlen(line);
     while (p > line && (p[-1] == '\n' || p[-1] == '\r'))
-        *p-- = 0;
+        *--p = 0;
 }
 
 static void parse_quoted_string(char *str)
@@ -66,14 +70,6 @@ static void parse_quoted_string(char *str)
 }
 
 
-static errcode_t parse_init_state(struct parse_state *state)
-{
-    state->state = STATE_INIT_COMMENT;
-    state->group_level = 0;
-
-    return profile_create_node("(root)", 0, &state->root_section);
-}
-
 static errcode_t parse_std_line(char *line, struct parse_state *state)
 {
     char    *cp, ch, *tag, *value;
@@ -201,10 +197,83 @@ static errcode_t parse_std_line(char *line, struct parse_state *state)
     return 0;
 }
 
+/* Open and parse an included profile file. */
+static errcode_t parse_include_file(char *filename, struct parse_state *state)
+{
+    FILE    *fp;
+    errcode_t retval = 0;
+    struct parse_state incstate;
+
+    /* Create a new state so that fragments are syntactically independent,
+     * sharing the root section with the existing state. */
+    incstate.state = STATE_INIT_COMMENT;
+    incstate.group_level = 0;
+    incstate.root_section = state->root_section;
+    incstate.current_section = NULL;
+
+    fp = fopen(filename, "r");
+    if (fp == NULL)
+        return PROF_FAIL_INCLUDE_FILE;
+    retval = parse_file(fp, &incstate);
+    fclose(fp);
+    return retval;
+}
+
+/* Return non-zero if filename contains only alphanumeric characters, dashes,
+ * and underscores. */
+static int valid_name(const char *filename)
+{
+    const char *p;
+
+    for (p = filename; *p != '\0'; p++) {
+        if (!isalnum((unsigned char)*p) && *p != '-' && *p != '_')
+            return 0;
+    }
+    return 1;
+}
+
+/*
+ * Include files within dirname.  Only files with names consisting entirely of
+ * alphanumeric chracters, dashes, and underscores are included, in order to
+ * avoid including editor backup files, .rpmsave files, and the like.
+ */
+static errcode_t parse_include_dir(char *dirname, struct parse_state *state)
+{
+    DIR     *dir;
+    char    *pathname;
+    errcode_t retval;
+    struct dirent *ent;
+
+    dir = opendir(dirname);
+    if (dir == NULL)
+        return PROF_FAIL_INCLUDE_DIR;
+    while ((ent = readdir(dir)) != NULL) {
+        if (!valid_name(ent->d_name))
+            continue;
+        if (asprintf(&pathname, "%s/%s", dirname, ent->d_name) < 0)
+            return ENOMEM;
+        retval = parse_include_file(pathname, state);
+        free(pathname);
+        if (retval)
+            return retval;
+    }
+    return 0;
+}
+
 static errcode_t parse_line(char *line, struct parse_state *state)
 {
     char    *cp;
 
+    if (strncmp(line, "include", 7) == 0 && isspace(line[7])) {
+        cp = skip_over_blanks(line + 7);
+        strip_line(cp);
+        return parse_include_file(cp, state);
+    }
+    if (strncmp(line, "includedir", 10) == 0 && isspace(line[10])) {
+        cp = skip_over_blanks(line + 10);
+        strip_line(cp);
+        return parse_include_dir(cp, state);
+    }
     switch (state->state) {
     case STATE_INIT_COMMENT:
         if (line[0] != '[')
@@ -221,29 +290,22 @@ static errcode_t parse_line(char *line, struct parse_state *state)
     return 0;
 }
 
-errcode_t profile_parse_file(FILE *f, struct profile_node **root)
+static errcode_t parse_file(FILE *f, struct parse_state *state)
 {
 #define BUF_SIZE        2048
     char *bptr;
     errcode_t retval;
-    struct parse_state state;
 
     bptr = malloc (BUF_SIZE);
     if (!bptr)
         return ENOMEM;
 
-    retval = parse_init_state(&state);
-    if (retval) {
-        free (bptr);
-        return retval;
-    }
     while (!feof(f)) {
         if (fgets(bptr, BUF_SIZE, f) == NULL)
             break;
 #ifndef PROFILE_SUPPORTS_FOREIGN_NEWLINES
-        retval = parse_line(bptr, &state);
+        retval = parse_line(bptr, state);
         if (retval) {
-            profile_free_node(state.root_section);
             free (bptr);
             return retval;
         }
@@ -286,9 +348,8 @@ errcode_t profile_parse_file(FILE *f, struct profile_node **root)
 
                 /* parse_line modifies contents of p */
                 newp = p + strlen (p) + 1;
-                retval = parse_line (p, &state);
+                retval = parse_line (p, state);
                 if (retval) {
-                    profile_free_node(state.root_section);
                     free (bptr);
                     return retval;
                 }
@@ -298,12 +359,35 @@ errcode_t profile_parse_file(FILE *f, struct profile_node **root)
         }
 #endif
     }
-    *root = state.root_section;
 
     free (bptr);
     return 0;
 }
 
+errcode_t profile_parse_file(FILE *f, struct profile_node **root)
+{
+    struct parse_state state;
+    errcode_t retval;
+
+    *root = NULL;
+
+    /* Initialize parsing state with a new root node. */
+    state.state = STATE_INIT_COMMENT;
+    state.group_level = 0;
+    state.current_section = NULL;
+    retval = profile_create_node("(root)", 0, &state.root_section);
+    if (retval)
+        return retval;
+
+    retval = parse_file(f, &state);
+    if (retval) {
+        profile_free_node(state.root_section);
+        return retval;
+    }
+    *root = state.root_section;
+    return 0;
+}
+
 /*
  * Return TRUE if the string begins or ends with whitespace
  */
index bd4901272dc563b245dae87b30892d6f8b157b5e..27ecbb2526b2543d83ff474b641215cb4775cde2 100644 (file)
@@ -147,8 +147,97 @@ proc test3 {} {
     puts "OK: test3: Clearing relation and adding one entry yields correct count."
 }
 
+# Exercise the include and includedir directives.
+proc test4 {} {
+    global wd verbose
+
+    # Test expected error message when including nonexistent file.
+    catch [file delete $wd/testinc.ini]
+    exec echo "include does-not-exist" >$wd/testinc.ini
+    catch { profile_init_path $wd/testinc.ini } err
+    if $verbose { puts "Got error message $err" }
+    if { $err ne "Included profile file could not be read" } {
+       puts stderr "Error: test4: Did not get expected error when including nonexistent file."
+       exit 1
+    }
+
+    # Test expected error message when including nonexistent directory.
+    catch [file delete $wd/testinc.ini]
+    exec echo "includedir does-not-exist" >$wd/testinc.ini
+    catch { profile_init_path $wd/testinc.ini } err
+    if $verbose { puts "Got error message $err" }
+    if { $err ne "Included profile directory could not be read" } {
+       puts stderr "Error: test4: Did not get expected error when including nonexistent directory."
+       exit 1
+    }
+
+    # Test including a file.
+    catch [file delete $wd/testinc.ini]
+    exec echo "include $wd/test2.ini" >$wd/testinc.ini
+    set p [profile_init_path $wd/testinc.ini]
+    set x [profile_get_values $p {{test section 1} bar}]
+    if $verbose { puts "Read $x from included profile" }
+    if { [lindex $x 0] ne "foo" } {
+       puts stderr "Error: test4: Did not get expected result from included profile."
+       exit 1
+    }
+    profile_release $p
+
+    # Test including a directory.  (Put two copies of test2.ini inside
+    # it and check that we get two values for one of the variables.)
+    catch [file delete -force $wd/test_include_dir]
+    exec mkdir $wd/test_include_dir
+    exec cp $wd/test2.ini $wd/test_include_dir/a
+    exec cp $wd/test2.ini $wd/test_include_dir/b
+    catch [file delete $wd/testinc.ini]
+    exec echo "includedir $wd/test_include_dir" >$wd/testinc.ini
+    set p [profile_init_path $wd/testinc.ini]
+    set x [profile_get_values $p {{test section 1} bar}]
+    if $verbose { puts "Read $x from included directory" }
+    if { $x ne "foo foo" } {
+       puts stderr, "Error: test4: Did not get expected result from included directory."
+       exit 1
+    }
+    profile_release $p
+
+    puts "OK: test4: include and includedir directives"
+}
+
+proc test5 {} {
+    global wd verbose
+
+    # Test syntactic independence of included profile files.
+    catch [file delete $wd/testinc.ini]
+    set f [open "$wd/testinc.ini" w]
+    puts $f {[sec1]}
+    puts $f "var = {"
+    puts $f "a = 1"
+    puts $f "include testinc2.ini"
+    puts $f "c = 3"
+    puts $f "}"
+    close $f
+    catch [file delete $wd/testinc2.ini]
+    set f [open "$wd/testinc2.ini" w]
+    puts $f {[sec2]}
+    puts $f "b = 2"
+    close $f
+    set p [profile_init_path $wd/testinc.ini]
+    set a [profile_get_values $p {sec1 var a}]
+    set b [profile_get_values $p {sec2 b}]
+    set c [profile_get_values $p {sec1 var c}]
+    if $verbose { puts "Read values [concat $a $b $c] from profile" }
+    if { $a != 1 || $b != 2 || $c != 3 } {
+       puts stderr, "Error: test5: Wrong results from profile"
+       exit 1
+    }
+
+    puts "OK: test5: syntax independence of included files"
+}
+
 test1
 test2
 test3
+test4
+test5
 
 exit 0