Beispiel #1
0
errno_t check_and_export_lifetime(struct dp_option *opts, const int opt_id,
                                  const char *env_name)
{
    int ret;
    char *str;
    krb5_deltat lifetime;
    bool free_str = false;

    str = dp_opt_get_string(opts, opt_id);
    if (str == NULL || *str == '\0') {
        DEBUG(5, ("No lifetime configured.\n"));
        return EOK;
    }

    if (isdigit(str[strlen(str)-1])) {
        str = talloc_asprintf(opts, "%ss", str);
        if (str == NULL) {
            DEBUG(1, ("talloc_asprintf failed\n"));
            return ENOMEM;
        }
        free_str = true;

        ret = dp_opt_set_string(opts, opt_id, str);
        if (ret != EOK) {
            DEBUG(1, ("dp_opt_set_string failed\n"));
            goto done;
        }
    }

    ret = krb5_string_to_deltat(str, &lifetime);
    if (ret != 0) {
        DEBUG(1, ("Invalid value [%s] for a lifetime.\n", str));
        ret = EINVAL;
        goto done;
    }

    ret = setenv(env_name, str, 1);
    if (ret != EOK) {
        ret = errno;
        DEBUG(2, ("setenv [%s] failed.\n", env_name));
        goto done;
    }

    ret = EOK;

done:
    if (free_str) {
        talloc_free(str);
    }

    return ret;
}
Beispiel #2
0
KRB5_LIB_FUNCTION void KRB5_LIB_CALL
krb5_appdefault_time(krb5_context context, const char *appname,
		     krb5_const_realm realm, const char *option,
		     time_t def_val, time_t *ret_val)
{
    krb5_deltat t;
    char *val;

    krb5_appdefault_string(context, appname, realm, option, NULL, &val);
    if (val == NULL) {
	*ret_val = def_val;
	return;
    }
    if (krb5_string_to_deltat(val, &t))
	*ret_val = def_val;
    else
	*ret_val = t;
    free(val);
}
Beispiel #3
0
/*
 * krb5_klog_init()	- Initialize logging.
 *
 * This routine parses the syntax described above to specify destinations for
 * com_err(3) or krb5_klog_syslog() messages generated by the caller.
 *
 * Parameters:
 *	kcontext	- Kerberos context.
 *	ename		- Entity name as it is to appear in the profile.
 *	whoami		- Entity name as it is to appear in error output.
 *	do_com_err	- Take over com_err(3) processing.
 *
 * Implicit inputs:
 *	stderr		- This is where STDERR output goes.
 *
 * Implicit outputs:
 *	log_nentries	- Number of log entries, both valid and invalid.
 *	log_control	- List of entries (log_nentries long) which contains
 *			  data for klog_com_err_proc() to use to determine
 *			  where/how to send output.
 */
krb5_error_code
krb5_klog_init(krb5_context kcontext, char *ename, char *whoami, krb5_boolean do_com_err)
{
    const char	*logging_profent[3];
    const char	*logging_defent[3];
    char	**logging_specs;
    int		i, ngood;
    char	*cp, *cp2;
    char	savec = '\0';
    int		error;
    int		do_openlog, log_facility;
    FILE	*f;
    mode_t      old_umask;

    /* Initialize */
    do_openlog = 0;
    log_facility = 0;

    err_context = kcontext;

    /*
     * Look up [logging]-><ename> in the profile.  If that doesn't
     * succeed, then look for [logging]->default.
     */
    logging_profent[0] = "logging";
    logging_profent[1] = ename;
    logging_profent[2] = (char *) NULL;
    logging_defent[0] = "logging";
    logging_defent[1] = "default";
    logging_defent[2] = (char *) NULL;
    logging_specs = (char **) NULL;
    ngood = 0;
    log_control.log_nentries = 0;
    if (!profile_get_values(kcontext->profile,
			    logging_profent,
			    &logging_specs) ||
	!profile_get_values(kcontext->profile,
			    logging_defent,
			    &logging_specs)) {
	/*
	 * We have a match, so we first count the number of elements
	 */
	for (log_control.log_nentries = 0;
	     logging_specs[log_control.log_nentries];
	     log_control.log_nentries++);

	/*
	 * Now allocate our structure.
	 */
	log_control.log_entries = (struct log_entry *)
	    malloc(log_control.log_nentries * sizeof(struct log_entry));
	if (log_control.log_entries) {
	    /*
	     * Scan through the list.
	     */
	    for (i=0; i<log_control.log_nentries; i++) {
		log_control.log_entries[i].log_type = K_LOG_NONE;
		log_control.log_entries[i].log_2free = logging_specs[i];
		/*
		 * The format is:
		 *	<whitespace><data><whitespace>
		 * so, trim off the leading and trailing whitespace here.
		 */
		for (cp = logging_specs[i]; isspace((int) *cp); cp++);
		for (cp2 = &logging_specs[i][strlen(logging_specs[i])-1];
		     isspace((int) *cp2); cp2--);
		cp2++;
		*cp2 = '\0';
		/*
		 * Is this a file?
		 */
		if (!strncasecmp(cp, "FILE", 4)) {
		    /*
		     * Check for append/overwrite, then open the file.
		     */
		    if (cp[4] == ':' || cp[4] == '=') {
			log_control.log_entries[i].lfu_fopen_mode = 
				(cp[4] == ':') ? "a+F" : "wF";
			old_umask = umask(077);
			f = fopen(&cp[5],
				log_control.log_entries[i].lfu_fopen_mode);
			umask(old_umask);
			if (f) {
                            char rotate_kw[128];

			    log_control.log_entries[i].lfu_filep = f;
			    log_control.log_entries[i].log_type = K_LOG_FILE;
			    log_control.log_entries[i].lfu_fname = &cp[5];
			    log_control.log_entries[i].lfu_rotate_period = 
				K_LOG_DEF_FILE_ROTATE_PERIOD;
			    log_control.log_entries[i].lfu_rotate_versions =
				K_LOG_DEF_FILE_ROTATE_VERSIONS;
			    log_control.log_entries[i].lfu_last_rotated =
				time(0);
				
			/*
			 * Now parse for ename_"rotate" = {
			 *	period = XXX
			 * 	versions = 10
			 * }
			 */
			    if (strlen(ename) + strlen("_rotate") <
				sizeof (rotate_kw)) {

				    char *time;
				    krb5_deltat	dt;
				    int vers;

				    strcpy(rotate_kw, ename);
				    strcat(rotate_kw, "_rotate");

				    if (!profile_get_string(kcontext->profile,
				        "logging", rotate_kw, "period",
					NULL, &time)) {

					if (time != NULL) {
					    if (!krb5_string_to_deltat(time,
						&dt)) {
			log_control.log_entries[i].lfu_rotate_period =
							(time_t) dt;
					    }
					    free(time);
					}
				    }
				
				    if (!profile_get_integer(
					kcontext->profile, "logging",
					rotate_kw, "versions",
					K_LOG_DEF_FILE_ROTATE_VERSIONS,
					&vers)) {
			log_control.log_entries[i].lfu_rotate_versions = vers;
				    }
			
			   }
			} else {
			    fprintf(stderr, gettext("Couldn't open log file %s: %s\n"),
				    &cp[5], error_message(errno));
			    continue;
			}
		    }
		}
#ifdef	HAVE_SYSLOG
		/*
		 * Is this a syslog?
		 */
		else if (!strncasecmp(cp, "SYSLOG", 6)) {
		    error = 0;
		    log_control.log_entries[i].lsu_facility = LOG_AUTH;
		    log_control.log_entries[i].lsu_severity = LOG_ERR;
		    /*
		     * Is there a severify specified?
		     */
		    if (cp[6] == ':') {
			/*
			 * Find the end of the severity.
			 */
			cp2 = strchr(&cp[7], ':');
			if (cp2) {
			    savec = *cp2;
			    *cp2 = '\0';
			    cp2++;
			}

			/*
			 * Match a severity.
			 */
			if (!strcasecmp(&cp[7], "ERR")) {
			    log_control.log_entries[i].lsu_severity = LOG_ERR;
			}
#ifdef	LOG_EMERG
			else if (!strcasecmp(&cp[7], "EMERG")) {
			    log_control.log_entries[i].lsu_severity =
				LOG_EMERG;
			}
#endif	/* LOG_EMERG */
#ifdef	LOG_ALERT
			else if (!strcasecmp(&cp[7], "ALERT")) {
			    log_control.log_entries[i].lsu_severity =
				LOG_ALERT;
			}
#endif	/* LOG_ALERT */
#ifdef	LOG_CRIT
			else if (!strcasecmp(&cp[7], "CRIT")) {
			    log_control.log_entries[i].lsu_severity = LOG_CRIT;
			}
#endif	/* LOG_CRIT */
#ifdef	LOG_WARNING
			else if (!strcasecmp(&cp[7], "WARNING")) {
			    log_control.log_entries[i].lsu_severity =
				LOG_WARNING;
			}
#endif	/* LOG_WARNING */
#ifdef	LOG_NOTICE
			else if (!strcasecmp(&cp[7], "NOTICE")) {
			    log_control.log_entries[i].lsu_severity =
				LOG_NOTICE;
			}
#endif	/* LOG_NOTICE */
#ifdef	LOG_INFO
			else if (!strcasecmp(&cp[7], "INFO")) {
			    log_control.log_entries[i].lsu_severity = LOG_INFO;
			}
#endif	/* LOG_INFO */
#ifdef	LOG_DEBUG
			else if (!strcasecmp(&cp[7], "DEBUG")) {
			    log_control.log_entries[i].lsu_severity =
				LOG_DEBUG;
			}
#endif	/* LOG_DEBUG */
			else
			    error = 1;

			/*
			 * If there is a facility present, then parse that.
			 */
			if (cp2) {
			    if (!strcasecmp(cp2, "AUTH")) {
				log_control.log_entries[i].lsu_facility = LOG_AUTH;
			    }
			    else if (!strcasecmp(cp2, "KERN")) {
				log_control.log_entries[i].lsu_facility = LOG_KERN;
			    }
			    else if (!strcasecmp(cp2, "USER")) {
				log_control.log_entries[i].lsu_facility = LOG_USER;
			    }
			    else if (!strcasecmp(cp2, "MAIL")) {
				log_control.log_entries[i].lsu_facility = LOG_MAIL;
			    }
			    else if (!strcasecmp(cp2, "DAEMON")) {
				log_control.log_entries[i].lsu_facility = LOG_DAEMON;
			    }
			    else if (!strcasecmp(cp2, "LPR")) {
				log_control.log_entries[i].lsu_facility = LOG_LPR;
			    }
			    else if (!strcasecmp(cp2, "NEWS")) {
				log_control.log_entries[i].lsu_facility = LOG_NEWS;
			    }
			    else if (!strcasecmp(cp2, "UUCP")) {
				log_control.log_entries[i].lsu_facility = LOG_UUCP;
			    }
			    else if (!strcasecmp(cp2, "CRON")) {
				log_control.log_entries[i].lsu_facility = LOG_CRON;
			    }
			    else if (!strcasecmp(cp2, "LOCAL0")) {
				log_control.log_entries[i].lsu_facility = LOG_LOCAL0;
			    }
			    else if (!strcasecmp(cp2, "LOCAL1")) {
				log_control.log_entries[i].lsu_facility = LOG_LOCAL1;
			    }
			    else if (!strcasecmp(cp2, "LOCAL2")) {
				log_control.log_entries[i].lsu_facility = LOG_LOCAL2;
			    }
			    else if (!strcasecmp(cp2, "LOCAL3")) {
				log_control.log_entries[i].lsu_facility = LOG_LOCAL3;
			    }
			    else if (!strcasecmp(cp2, "LOCAL4")) {
				log_control.log_entries[i].lsu_facility = LOG_LOCAL4;
			    }
			    else if (!strcasecmp(cp2, "LOCAL5")) {
				log_control.log_entries[i].lsu_facility = LOG_LOCAL5;
			    }
			    else if (!strcasecmp(cp2, "LOCAL6")) {
				log_control.log_entries[i].lsu_facility = LOG_LOCAL6;
			    }
			    else if (!strcasecmp(cp2, "LOCAL7")) {
				log_control.log_entries[i].lsu_facility = LOG_LOCAL7;
			    }
			    cp2--;
			    *cp2 = savec;
			}
		    }
		    if (!error) {
			log_control.log_entries[i].log_type = K_LOG_SYSLOG;
			do_openlog = 1;
			log_facility = log_control.log_entries[i].lsu_facility;
		    }
		}
#endif	/* HAVE_SYSLOG */
		/*
		 * Is this a standard error specification?
		 */
		else if (!strcasecmp(cp, "STDERR")) {
		    log_control.log_entries[i].lfu_filep =
			fdopen(fileno(stderr), "a+F");
		    if (log_control.log_entries[i].lfu_filep) {
			log_control.log_entries[i].log_type = K_LOG_STDERR;
			log_control.log_entries[i].lfu_fname =
			    "standard error";
		    }
		}
		/*
		 * Is this a specification of the console?
		 */
		else if (!strcasecmp(cp, "CONSOLE")) {
		    log_control.log_entries[i].ldu_filep =
			CONSOLE_OPEN("a+F");
		    if (log_control.log_entries[i].ldu_filep) {
			log_control.log_entries[i].log_type = K_LOG_CONSOLE;
			log_control.log_entries[i].ldu_devname = "console";
		    }
		}
		/*
		 * Is this a specification of a device?
		 */
		else if (!strncasecmp(cp, "DEVICE", 6)) {
		    /*
		     * We handle devices very similarly to files.
		     */
		    if (cp[6] == '=') {
			log_control.log_entries[i].ldu_filep = 
			    DEVICE_OPEN(&cp[7], "wF");
			if (log_control.log_entries[i].ldu_filep) {
			    log_control.log_entries[i].log_type = K_LOG_DEVICE;
			    log_control.log_entries[i].ldu_devname = &cp[7];
			}
		    }
		}
		/*
		 * See if we successfully parsed this specification.
		 */
		if (log_control.log_entries[i].log_type == K_LOG_NONE) {
		    fprintf(stderr, krb5_log_error_table(LSPEC_PARSE_ERR_1), whoami, cp);
		    fprintf(stderr, krb5_log_error_table(LSPEC_PARSE_ERR_2), whoami);
		}
		else
		    ngood++;
	    }
	}
	/*
	 * If we didn't find anything, then free our lists.
	 */
	if (ngood == 0) {
	    for (i=0; i<log_control.log_nentries; i++)
		free(logging_specs[i]);
	}
	free(logging_specs);
    }
    /*
     * If we didn't find anything, go for the default which is to log to
     * the system log.
     */
    if (ngood == 0) {
	if (log_control.log_entries)
	    free(log_control.log_entries);
	log_control.log_entries = &def_log_entry;
	log_control.log_entries->log_type = K_LOG_SYSLOG;
	log_control.log_entries->log_2free = (krb5_pointer) NULL;
	log_facility = log_control.log_entries->lsu_facility = LOG_AUTH;
	log_control.log_entries->lsu_severity = LOG_ERR;
	do_openlog = 1;
	log_control.log_nentries = 1;
    }
    if (log_control.log_nentries) {
	log_control.log_whoami = (char *) malloc(strlen(whoami)+1);
	if (log_control.log_whoami)
	    strcpy(log_control.log_whoami, whoami);

	log_control.log_hostname = (char *) malloc(MAXHOSTNAMELEN + 1);
	if (log_control.log_hostname) {
	    gethostname(log_control.log_hostname, MAXHOSTNAMELEN);
	    log_control.log_hostname[MAXHOSTNAMELEN] = '\0';
	}
#ifdef	HAVE_OPENLOG
	if (do_openlog) {
	    openlog(whoami, LOG_NDELAY|LOG_PID, log_facility);
	    log_control.log_opened = 1;
	}
#endif /* HAVE_OPENLOG */
	if (do_com_err)
	    (void) set_com_err_hook(klog_com_err_proc);
    }
    return((log_control.log_nentries) ? 0 : ENOENT);
}
Beispiel #4
0
errno_t krb5_child_init(struct krb5_ctx *krb5_auth_ctx,
                        struct be_ctx *bectx)
{
    errno_t ret;
    time_t renew_intv = 0;
    krb5_deltat renew_interval_delta;
    char *renew_interval_str;

    if (dp_opt_get_bool(krb5_auth_ctx->opts, KRB5_STORE_PASSWORD_IF_OFFLINE)) {
        ret = init_delayed_online_authentication(krb5_auth_ctx, bectx,
                                                 bectx->ev);
        if (ret != EOK) {
            DEBUG(SSSDBG_CRIT_FAILURE,
                  "init_delayed_online_authentication failed.\n");
            goto done;
        }
    }
    renew_interval_str = dp_opt_get_string(krb5_auth_ctx->opts,
                         KRB5_RENEW_INTERVAL);
    if (renew_interval_str != NULL) {
        ret = krb5_string_to_deltat(renew_interval_str, &renew_interval_delta);
        if (ret != EOK) {
            DEBUG(SSSDBG_MINOR_FAILURE,
                 "Reading krb5_renew_interval failed.\n");
            renew_interval_delta = 0;
        }
        renew_intv = renew_interval_delta;
    }

    if (renew_intv > 0) {
        ret = init_renew_tgt(krb5_auth_ctx, bectx, bectx->ev, renew_intv);
        if (ret != EOK) {
            DEBUG(SSSDBG_CRIT_FAILURE, "init_renew_tgt failed.\n");
            goto done;
        }
    }

    ret = check_and_export_options(krb5_auth_ctx->opts, bectx->domain,
                                   krb5_auth_ctx);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE, "check_and_export_opts failed.\n");
        goto done;
    }

    ret = krb5_install_offline_callback(bectx, krb5_auth_ctx);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE, "krb5_install_offline_callback failed.\n");
        goto done;
    }

    ret = krb5_install_sigterm_handler(bectx->ev, krb5_auth_ctx);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE, "krb5_install_sigterm_handler failed.\n");
        goto done;
    }

    krb5_auth_ctx->child_debug_fd = -1; /* -1 means not initialized */
    ret = child_debug_init(KRB5_CHILD_LOG_FILE,
                           &krb5_auth_ctx->child_debug_fd);
    if (ret != EOK) {
        DEBUG(SSSDBG_OP_FAILURE, "Could not set krb5_child debugging!\n");
        goto done;
    }

    ret = parse_krb5_map_user(krb5_auth_ctx,
                              dp_opt_get_cstring(krb5_auth_ctx->opts,
                                                 KRB5_MAP_USER),
                              bectx->domain->name,
                              &krb5_auth_ctx->name_to_primary);
    if (ret != EOK) {
        DEBUG(SSSDBG_OP_FAILURE, "parse_krb5_map_user failed: %s:[%d]\n",
              sss_strerror(ret), ret);
        goto done;
    }

    ret = EOK;

done:
    return ret;
}
Beispiel #5
0
/*
 * Parse a restrictions field.  Return NULL on failure.
 *
 * Allowed restrictions are:
 *      [+-]flagname            (recognized by krb5_flagspec_to_mask)
 *                              flag is forced to indicated value
 *      -clearpolicy            policy is forced clear
 *      -policy pol             policy is forced to be "pol"
 *      -{expire,pwexpire,maxlife,maxrenewlife} deltat
 *                              associated value will be forced to
 *                              MIN(deltat, requested value)
 */
static struct kadm5_auth_restrictions *
parse_restrictions(const char *str, const char *fname)
{
    char *copy = NULL, *token, *arg, *save;
    const char *delims = "\t\n\f\v\r ,";
    krb5_deltat delta;
    struct kadm5_auth_restrictions *rs;

    copy = strdup(str);
    if (copy == NULL)
        return NULL;

    rs = calloc(1, sizeof(*rs));
    if (rs == NULL) {
        free(copy);
        return NULL;
    }

    rs->forbid_attrs = ~(krb5_flags)0;
    for (token = strtok_r(copy, delims, &save); token != NULL;
         token = strtok_r(NULL, delims, &save)) {

        if (krb5_flagspec_to_mask(token, &rs->require_attrs,
                                  &rs->forbid_attrs) == 0) {
            rs->mask |= KADM5_ATTRIBUTES;
            continue;
        }

        if (strcmp(token, "-clearpolicy") == 0) {
            rs->mask |= KADM5_POLICY_CLR;
            continue;
        }

        /* Everything else needs an argument. */
        arg = strtok_r(NULL, delims, &save);
        if (arg == NULL)
            goto error;

        if (strcmp(token, "-policy") == 0) {
            if (rs->policy != NULL)
                goto error;
            rs->policy = strdup(arg);
            if (rs->policy == NULL)
                goto error;
            rs->mask |= KADM5_POLICY;
            continue;
        }

        /* All other arguments must be a deltat. */
        if (krb5_string_to_deltat(arg, &delta) != 0)
            goto error;

        if (strcmp(token, "-expire") == 0) {
            rs->princ_lifetime = delta;
            rs->mask |= KADM5_PRINC_EXPIRE_TIME;
        } else if (strcmp(token, "-pwexpire") == 0) {
            rs->pw_lifetime = delta;
            rs->mask |= KADM5_PW_EXPIRATION;
        } else if (strcmp(token, "-maxlife") == 0) {
            rs->max_life = delta;
            rs->mask |= KADM5_MAX_LIFE;
        } else if (strcmp(token, "-maxrenewlife") == 0) {
            rs->max_renewable_life = delta;
            rs->mask |= KADM5_MAX_RLIFE;
        } else {
            goto error;
        }
    }

    free(copy);
    return rs;

error:
    krb5_klog_syslog(LOG_ERR, _("%s: invalid restrictions: %s"), fname, str);
    free(copy);
    free(rs->policy);
    free(rs);
    return NULL;
}
Beispiel #6
0
errno_t krb5_child_init(struct krb5_ctx *krb5_auth_ctx,
                        struct be_ctx *bectx)
{
    errno_t ret;
    FILE *debug_filep;
    time_t renew_intv = 0;
    krb5_deltat renew_interval_delta;
    char *renew_interval_str;

    if (dp_opt_get_bool(krb5_auth_ctx->opts, KRB5_STORE_PASSWORD_IF_OFFLINE)) {
        ret = init_delayed_online_authentication(krb5_auth_ctx, bectx,
                                                 bectx->ev);
        if (ret != EOK) {
            DEBUG(SSSDBG_CRIT_FAILURE,
                  "init_delayed_online_authentication failed.\n");
            goto done;
        }
    }
    renew_interval_str = dp_opt_get_string(krb5_auth_ctx->opts,
                         KRB5_RENEW_INTERVAL);
    if (renew_interval_str != NULL) {
        ret = krb5_string_to_deltat(renew_interval_str, &renew_interval_delta);
        if (ret != EOK) {
            DEBUG(SSSDBG_MINOR_FAILURE,
                 "Reading krb5_renew_interval failed.\n");
            renew_interval_delta = 0;
        }
        renew_intv = renew_interval_delta;
    }

    if (renew_intv > 0) {
        ret = init_renew_tgt(krb5_auth_ctx, bectx, bectx->ev, renew_intv);
        if (ret != EOK) {
            DEBUG(SSSDBG_CRIT_FAILURE, "init_renew_tgt failed.\n");
            goto done;
        }
    }

    ret = check_and_export_options(krb5_auth_ctx->opts, bectx->domain,
                                   krb5_auth_ctx);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE, "check_and_export_opts failed.\n");
        goto done;
    }

    ret = krb5_install_offline_callback(bectx, krb5_auth_ctx);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE, "krb5_install_offline_callback failed.\n");
        goto done;
    }

    ret = krb5_install_sigterm_handler(bectx->ev, krb5_auth_ctx);
    if (ret != EOK) {
        DEBUG(SSSDBG_CRIT_FAILURE, "krb5_install_sigterm_handler failed.\n");
        goto done;
    }

    if (debug_to_file != 0) {
        ret = open_debug_file_ex(KRB5_CHILD_LOG_FILE, &debug_filep, false);
        if (ret != EOK) {
            DEBUG(SSSDBG_FATAL_FAILURE, "Error setting up logging (%d) [%s]\n",
                    ret, strerror(ret));
            goto done;
        }

        krb5_auth_ctx->child_debug_fd = fileno(debug_filep);
        if (krb5_auth_ctx->child_debug_fd == -1) {
            DEBUG(SSSDBG_FATAL_FAILURE,
                  "fileno failed [%d][%s]\n", errno, strerror(errno));
            ret = errno;
            goto done;
        }
    }

done:
    return ret;
}
Beispiel #7
0
int
main (void)
{
    struct {
        char *string;
        krb5_deltat expected;
        int is_error;
#define GOOD(STR,VAL) { STR, VAL, 0 }
#define BAD(STR) { STR, 0, 1 }
#define DAY (24 * 3600)
#define HOUR 3600
#ifdef MIN
#undef MIN
#endif
#define MIN 60
    } values[] = {
        /* d-h-m-s patterns */
        GOOD ("3d", 3*DAY),
        GOOD ("3h", 3*HOUR),
        GOOD ("3m", 3*MIN),
        GOOD ("3s", 3),
        BAD ("3dd"),
        GOOD ("3d4m    42s", 3 * DAY + 4 * MIN + 42),
        GOOD ("3d-1h", 3 * DAY - 1 * HOUR),
        GOOD ("3d -1h", 3 * DAY - HOUR),
        GOOD ("3d4h5m6s", 3 * DAY + 4 * HOUR + 5 * MIN + 6),
        BAD ("3d4m5h"),
        GOOD ("12345s", 12345),
        GOOD ("1m 12345s", MIN + 12345),
        GOOD ("1m12345s", MIN + 12345),
        GOOD ("3d 0m", 3 * DAY),
        GOOD ("3d 0m  ", 3 * DAY),
        GOOD ("3d \n\t 0m  ", 3 * DAY),
        /* colon patterns */
        GOOD ("42-13:42:47", 42 * DAY + 13 * HOUR + 42 * MIN + 47),
        BAD ("3: 4"),
        BAD ("13:0003"),
        GOOD ("12:34", 12 * HOUR + 34 * MIN),
        GOOD ("1:02:03", 1 * HOUR + 2 * MIN + 3),
        BAD ("3:-4"),
        /* XX We might want to require exactly two digits after a colon?  */
        GOOD ("3:4", 3 * HOUR + 4 * MIN),
        /* misc */
        GOOD ("42", 42),
        BAD ("1-2"),
        /* Test overflow limitations */
        GOOD ("2147483647s", 2147483647),
        BAD ("2147483648s"),
        GOOD ("24855d", 24855 * DAY),
        BAD ("24856d"),
        BAD ("24855d 100000000h"),
        GOOD ("24855d 3h", 24855 * DAY + 3 * HOUR),
        BAD ("24855d 4h"),
        GOOD ("24855d 11647s", 24855 * DAY + 11647),
        BAD ("24855d 11648s"),
        GOOD ("24855d 194m 7s", 24855 * DAY + 194 * MIN + 7),
        BAD ("24855d 194m 8s"),
        BAD ("24855d 195m"),
        BAD ("24855d 19500000000m"),
        GOOD ("24855d 3h 14m 7s", 24855 * DAY + 3 * HOUR + 14 * MIN + 7),
        BAD ("24855d 3h 14m 8s"),
        GOOD ("596523h", 596523 * HOUR),
        BAD ("596524h"),
        GOOD ("596523h 847s", 596523 * HOUR + 847),
        BAD ("596523h 848s"),
        GOOD ("596523h 14m 7s", 596523 * HOUR + 14 * MIN + 7),
        BAD ("596523h 14m 8s"),
        GOOD ("35791394m", 35791394 * MIN),
        GOOD ("35791394m7s", 35791394 * MIN + 7),
        BAD ("35791394m8s"),
        /* Test underflow */
        GOOD ("-2147483647s", -2147483647),
        /* This should be valid, but isn't */
        /*BAD ("-2147483648s"),*/
        GOOD ("-24855d", -24855 * DAY),
        BAD ("-24856d"),
        BAD ("-24855d -100000000h"),
        GOOD ("-24855d -3h", -24855 * DAY - 3 * HOUR),
        BAD ("-24855d -4h"),
        GOOD ("-24855d -11647s", -24855 * DAY - 11647),
        BAD ("-24855d -11649s"),
        GOOD ("-24855d -194m -7s", -24855 * DAY - 194 * MIN - 7),
        BAD ("-24855d -194m -9s"),
        BAD ("-24855d -195m"),
        BAD ("-24855d -19500000000m"),
        GOOD ("-24855d -3h -14m -7s", -24855 * DAY - 3 * HOUR - 14 * MIN - 7),
        BAD ("-24855d -3h -14m -9s"),
        GOOD ("-596523h", -596523 * HOUR),
        BAD ("-596524h"),
        GOOD ("-596523h -847s", -596523 * HOUR - 847),
        GOOD ("-596523h -848s", -596523 * HOUR - 848),
        BAD ("-596523h -849s"),
        GOOD ("-596523h -14m -8s", -596523 * HOUR - 14 * MIN - 8),
        BAD ("-596523h -14m -9s"),
        GOOD ("-35791394m", -35791394 * MIN),
        GOOD ("-35791394m7s", -35791394 * MIN + 7),
        BAD ("-35791394m-9s"),

    };
    int fail = 0;
    size_t i;

    for (i = 0; i < sizeof(values)/sizeof(values[0]); i++) {
        krb5_deltat result;
        krb5_error_code code;

        code = krb5_string_to_deltat (values[i].string, &result);
        if (code && !values[i].is_error) {
            fprintf (stderr, "unexpected error for `%s'\n", values[i].string);
            fail++;
        } else if (!code && values[i].is_error) {
            fprintf (stderr, "expected but didn't get error for `%s'\n",
                     values[i].string);
            fail++;
        } else if (code && values[i].is_error) {
            /* do nothing */
        } else if (result != values[i].expected) {
            fprintf (stderr, "got %ld instead of expected %ld for `%s'\n",
                     (long) result, (long) values[i].expected,
                     values[i].string);
            fail++;
        }
    }
    if (fail == 0)
        printf ("Passed all %d tests.\n", (int)i);
    else
        printf ("Failed %d of %d tests.\n", fail, (int)i);
    return fail;
}