From: Greg Hudson Date: Thu, 26 Aug 2010 23:19:40 +0000 (+0000) Subject: Merge trunk changes from r24202 to r24258 to plugins2 branch X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b0a621a2746f66ce1474aa3bf4a1a42dbc95992b;p=thirdparty%2Fkrb5.git Merge trunk changes from r24202 to r24258 to plugins2 branch git-svn-id: svn://anonsvn.mit.edu/krb5/branches/plugins2@24259 dc483132-0cff-0310-8789-dd5450dbe970 --- diff --git a/doc/krb5conf.texinfo b/doc/krb5conf.texinfo index 09825524f5..9114350619 100644 --- a/doc/krb5conf.texinfo +++ b/doc/krb5conf.texinfo @@ -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: diff --git a/src/Makefile.in b/src/Makefile.in index 35040a1eeb..3d342bc3d1 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -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 $@ diff --git a/src/config-files/krb5.conf.M b/src/config-files/krb5.conf.M index 5ecfd426c7..db3305f598 100644 --- a/src/config-files/krb5.conf.M +++ b/src/config-files/krb5.conf.M @@ -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 diff --git a/src/include/Makefile.in b/src/include/Makefile.in index 1f3564a7c6..5c178a24af 100644 --- a/src/include/Makefile.in +++ b/src/include/Makefile.in @@ -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:: diff --git a/src/include/k5-int.h b/src/include/k5-int.h index 89bc79d077..f110179cec 100644 --- a/src/include/k5-int.h +++ b/src/include/k5-int.h @@ -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; /* diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin index 7d7e425a3e..f49ef95e20 100644 --- a/src/include/krb5/krb5.hin +++ b/src/include/krb5/krb5.hin @@ -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, diff --git a/src/kdc/do_as_req.c b/src/kdc/do_as_req.c index 48a55e4c71..fa98ae3a0d 100644 --- a/src/kdc/do_as_req.c +++ b/src/kdc/do_as_req.c @@ -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 index e27d656e6b..0000000000 --- a/src/lib/gssapi/generic/util_canonhost.c +++ /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 -#endif -#include -#include - -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 index 7956a07226..0000000000 --- a/src/lib/gssapi/generic/util_localhost.c +++ /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 - -#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); -} diff --git a/src/lib/gssapi/krb5/acquire_cred.c b/src/lib/gssapi/krb5/acquire_cred.c index 28e25052ab..bceab6173f 100644 --- a/src/lib/gssapi/krb5/acquire_cred.c +++ b/src/lib/gssapi/krb5/acquire_cred.c @@ -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; } diff --git a/src/lib/gssapi/krb5/krb5_gss_glue.c b/src/lib/gssapi/krb5/krb5_gss_glue.c index 0d87f90c96..d2a47acb84 100644 --- a/src/lib/gssapi/krb5/krb5_gss_glue.c +++ b/src/lib/gssapi/krb5/krb5_gss_glue.c @@ -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, diff --git a/src/lib/kadm5/kadm_rpc_xdr.c b/src/lib/kadm5/kadm_rpc_xdr.c index 36724eab3d..5fb67ebb1c 100644 --- a/src/lib/kadm5/kadm_rpc_xdr.c +++ b/src/lib/kadm5/kadm_rpc_xdr.c @@ -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); } diff --git a/src/lib/krb5/error_tables/Makefile.in b/src/lib/krb5/error_tables/Makefile.in index 497236326a..d340b7b150 100644 --- a/src/lib/krb5/error_tables/Makefile.in +++ b/src/lib/krb5/error_tables/Makefile.in @@ -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 index 0000000000..6d5621001a --- /dev/null +++ b/src/lib/krb5/error_tables/k5e1_err.et @@ -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 diff --git a/src/lib/krb5/krb/Makefile.in b/src/lib/krb5/krb/Makefile.in index 0c013862d5..1b4d879677 100644 --- a/src/lib/krb5/krb/Makefile.in +++ b/src/lib/krb5/krb/Makefile.in @@ -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) \ diff --git a/src/lib/krb5/krb/gic_opt.c b/src/lib/krb5/krb/gic_opt.c index ab29740bb6..36f4f00a1f 100644 --- a/src/lib/krb5/krb/gic_opt.c +++ b/src/lib/krb5/krb/gic_opt.c @@ -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; +} diff --git a/src/lib/krb5/krb/gic_pwd.c b/src/lib/krb5/krb/gic_pwd.c index 6df9014986..1e0b741e38 100644 --- a/src/lib/krb5/krb/gic_pwd.c +++ b/src/lib/krb5/krb/gic_pwd.c @@ -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); diff --git a/src/lib/krb5/krb/rd_req_dec.c b/src/lib/krb5/krb/rd_req_dec.c index 014002981b..9bc7c42f90 100644 --- a/src/lib/krb5/krb/rd_req_dec.c +++ b/src/lib/krb5/krb/rd_req_dec.c @@ -44,7 +44,14 @@ * * 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 index 0000000000..6e8d87ce14 --- /dev/null +++ b/src/lib/krb5/krb/t_expire_warn.c @@ -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 index 0000000000..dc49a4c032 --- /dev/null +++ b/src/lib/krb5/krb/t_expire_warn.py @@ -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.') diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports index b375c1be06..57e5c45de2 100644 --- a/src/lib/krb5/libkrb5.exports +++ b/src/lib/krb5/libkrb5.exports @@ -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 diff --git a/src/lib/rpc/xdr.c b/src/lib/rpc/xdr.c index ff67e90f6d..8689ee3166 100644 --- a/src/lib/rpc/xdr.c +++ b/src/lib/rpc/xdr.c @@ -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; diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c b/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c index 1f3e60e926..e9d50196de 100644 --- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c +++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_misc.c @@ -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 diff --git a/src/util/k5test.py b/src/util/k5test.py index e506afa7c9..d88086274b 100644 --- a/src/util/k5test.py +++ b/src/util/k5test.py @@ -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. diff --git a/src/util/profile/prof_err.et b/src/util/profile/prof_err.et index af7801ee0a..2384127af9 100644 --- a/src/util/profile/prof_err.et +++ b/src/util/profile/prof_err.et @@ -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 diff --git a/src/util/profile/prof_init.c b/src/util/profile/prof_init.c index bd42b13805..408549dca0 100644 --- a/src/util/profile/prof_init.c +++ b/src/util/profile/prof_init.c @@ -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; } } diff --git a/src/util/profile/prof_parse.c b/src/util/profile/prof_parse.c index 413c7dfbb0..7f3d4c9d49 100644 --- a/src/util/profile/prof_parse.c +++ b/src/util/profile/prof_parse.c @@ -1,6 +1,7 @@ /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ #include "prof_int.h" +#include #include #include #ifdef HAVE_STDLIB_H @@ -8,6 +9,7 @@ #endif #include #include +#include #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 */ diff --git a/src/util/profile/prof_test1 b/src/util/profile/prof_test1 index bd4901272d..27ecbb2526 100644 --- a/src/util/profile/prof_test1 +++ b/src/util/profile/prof_test1 @@ -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