/* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * RPC server procedures for the usermode daemon kwarnd. */ #include <stdio.h> #include <unistd.h> #include <pwd.h> #include <grp.h> #include <strings.h> #include <string.h> #include <sys/param.h> #include <sys/syslog.h> #include "kwarnd.h" #include <rpc/rpc.h> #include <stdlib.h> #include <syslog.h> #include <poll.h> #include <utmpx.h> #include <pwd.h> #include <strings.h> #include <ctype.h> #include <k5-int.h> #include <profile/prof_int.h> #include <com_err.h> #include <libintl.h> #include <krb5.h> struct k5_data { krb5_context ctx; krb5_ccache cc; krb5_principal me; char *name; }; #define MAIL "mail" #define MAILPATH "/usr/bin/mail" #define DEFAULT_CONFIG "* terminal 30m" #define CONF_FILENAME "/etc/krb5/warn.conf" /* warn.conf info */ typedef struct config_entry_s { struct config_entry_s *next; int seconds_to_warn; char *principal; char *where_to; char *email; int renew; int log_success; int log_failure; } config_entry_list_t; static config_entry_list_t *config_entry_list; /* list of principals to be warned */ typedef struct cred_warning_list_s { struct cred_warning_list_s *next; WARNING_NAME_T warn_name; time_t cred_exp_time; time_t cred_warn_time; mutex_t cwm; } cred_warning_list_t; static cred_warning_list_t *cred_warning_list; static rwlock_t cred_lock = DEFAULTRWLOCK; static bool_t del_warning_pvt(char *); static config_entry_list_t * find_warning_info(char *); static bool_t parseConfigLine(char *buffer); extern warn_send(char *, char *); extern int kwarnd_debug; cred_warning_list_t * find_cred_warning(WARNING_NAME_T warn_name) { cred_warning_list_t *cw; if (!cred_warning_list) return (NULL); for (cw = cred_warning_list; cw != NULL; cw = cw->next) { if (strcmp(warn_name, cw->warn_name) != 0) continue; return (cw); } return (NULL); } /* * add a principal to the principal warning list */ bool_t kwarn_add_warning_1_svc(kwarn_add_warning_arg *argp, kwarn_add_warning_res *res, struct svc_req *rqstp) { cred_warning_list_t *cred_warning; config_entry_list_t *config_entry; if (kwarnd_debug) { printf("kwarn_add_warning_1_svc start; cWlist=%p\n", cred_warning_list); printf("kwarn_add_warning_1_svc: principal %s", argp->warning_name); printf(" exp time: %d\n", argp->cred_exp_time); } /* * if there is no entry in the config file that matches the principal to * be added to the warning list, return true because we are not going to * send a warning for this principal. */ if ((config_entry = find_warning_info(argp->warning_name)) == NULL) { if (kwarnd_debug) printf( "kwarn_add_warning_1_svc find_warn_info: fails, cWlist=%p\n", cred_warning_list); return (TRUE); } /* * see if a warning has already been created for this principal, if so * update the warning time. */ rw_wrlock(&cred_lock); if (cred_warning = find_cred_warning(argp->warning_name)) { rw_unlock(&cred_lock); mutex_lock(&cred_warning->cwm); cred_warning->cred_exp_time = argp->cred_exp_time; cred_warning->cred_warn_time = argp->cred_exp_time - config_entry->seconds_to_warn; mutex_unlock(&cred_warning->cwm); } else { cred_warning = (cred_warning_list_t *)malloc( sizeof (*cred_warning_list)); if (cred_warning == NULL) { rw_unlock(&cred_lock); res->status = 1; return (FALSE); } (void) memset((char *)cred_warning, 0, sizeof (*cred_warning_list)); cred_warning->cred_exp_time = argp->cred_exp_time; cred_warning->cred_warn_time = argp->cred_exp_time - config_entry->seconds_to_warn; cred_warning->warn_name = strdup(argp->warning_name); if (cred_warning->warn_name == NULL) { free(cred_warning); rw_unlock(&cred_lock); res->status = 1; return (FALSE); } mutex_init(&cred_warning->cwm, USYNC_THREAD, NULL); cred_warning->next = cred_warning_list; cred_warning_list = cred_warning; rw_unlock(&cred_lock); } res->status = 0; if (kwarnd_debug) printf( "kwarn_add_warning_1_svc end: returns true; cWlist=%p\n", cred_warning_list); return (TRUE); } /* * delete a warning request for a given principal */ bool_t kwarn_del_warning_1_svc(kwarn_del_warning_arg *argp, kwarn_del_warning_res *res, struct svc_req *rqstp) { if (kwarnd_debug) printf(gettext("delete principal %s requested\n"), argp->warning_name); if (del_warning_pvt(argp->warning_name) == TRUE) { res->status = 0; if (kwarnd_debug) printf(gettext("delete principal %s completed\n"), argp->warning_name); return (TRUE); } else { res->status = 1; if (kwarnd_debug) printf(gettext("delete principal %s failed\n"), argp->warning_name); return (TRUE); } } static bool_t del_warning_pvt(char *warning_name) { cred_warning_list_t *cred_warning, *prev; rw_wrlock(&cred_lock); for (prev = NULL, cred_warning = cred_warning_list; cred_warning != NULL; prev = cred_warning, cred_warning = cred_warning->next) { if (strcmp(cred_warning->warn_name, warning_name) == 0) { if (!prev) cred_warning_list = cred_warning->next; else prev->next = cred_warning->next; free(cred_warning->warn_name); free(cred_warning); rw_unlock(&cred_lock); return (TRUE); } } rw_unlock(&cred_lock); return (FALSE); } /* * load the warn.conf file into the config_entry list. */ bool_t loadConfigFile(void) { char buffer[BUFSIZ]; FILE *cfgfile; bool_t retval = TRUE; if ((cfgfile = fopen(CONF_FILENAME, "r")) == NULL) { syslog(LOG_ERR, gettext( "could not open config file \"%s\"\n"), CONF_FILENAME); syslog(LOG_ERR, gettext( "using default options \"%s\"\n"), DEFAULT_CONFIG); retval = parseConfigLine(DEFAULT_CONFIG); } else { (void) memset(buffer, 0, sizeof (buffer)); while ((fgets(buffer, BUFSIZ, cfgfile) != NULL) && (retval == TRUE)) retval = parseConfigLine(buffer); fclose(cfgfile); } return (retval); } /* * Return TRUE if we get a valid opt and update flags appro. */ static bool_t cmp_renew_opts(char *opt, int *log_success, /* out */ int *log_failure) /* out */ { if (strncasecmp(opt, "log", sizeof ("log")) == 0) { *log_success = *log_failure = 1; } else if (strncasecmp(opt, "log-success", sizeof ("log-success")) == 0) { *log_success = 1; } else if (strncasecmp(opt, "log-failure", sizeof ("log-failure")) == 0) { *log_failure = 1; } else { if (kwarnd_debug) printf("cmp_renew_opts: renew bad opt=`%s'\n", opt ? opt : "null"); return (FALSE); } return (TRUE); } /* * Make the config_entry item for the config_entry_list, based on * buffer. The formats are * * <principal> [renew[:<opt1,...optN>]] syslog|terminal <time> * <principal> [renew[:<opt1,...optN>]] mail <time> <e-mail address> * * where renew opts will be: * * log-success * - Log the result of the renew attempt on success using * the specified method (syslog|terminal|mail) * * log-failure * - Log the result of the renew attempt on failure using * the specified method (syslog|terminal|mail) * * log * - Same as specifing both log-failure and log-success * * Note if no log options are given, there will be no logging. * */ static bool_t parseConfigLine(char *buffer) { char *principal, *send_to, *emailid, *ends, *tm; char *exptime; int time_mode; time_t etime; config_entry_list_t *config_entry; int renew = 0; int log_success = 0; int log_failure = 0; /* ignore comments */ if (*buffer == '#') return (TRUE); if (kwarnd_debug) printf("parseconf: buffer=%s", buffer); /* find end of principal */ principal = buffer; for (send_to = buffer; *send_to && !isspace(*send_to); send_to++); /* find first non whitespace after principal (start of send_to) */ if (*send_to) { *send_to = '\0'; send_to++; while (*send_to && isspace(*send_to)) send_to++; } /* if no send_to, continue, bad entry */ if (! *send_to) return (TRUE); /* find end of send_to */ for (ends = send_to; *ends && !isspace(*ends); ends++); if (*ends) *ends = '\0'; if (strchr(send_to, ':')) { /* we've got renew opts */ char *st = NULL, *op = NULL; op = strdup(send_to); if (!op) return (FALSE); st = strchr(op, ':'); *st = '\0'; if (strncasecmp(op, "renew", sizeof ("renew")) == 0) { renew = 1; } else { free(op); /* got a ':' but not preceeded w/renew, badent, skip */ if (kwarnd_debug) printf("parseconf: colon badent, skip\n"); return (TRUE); } free(op); op = NULL; st++; if (!st || !*st || isspace(*st)) { if (kwarnd_debug) printf("parseconf: st badent, skip\n"); /* bad ent, skip */ return (TRUE); } if (renew && strchr(st, ',')) { while (1) { /* loop thru comma seperated list-o-opts */ char *comma = NULL, *c = NULL, *l = NULL; if (st && (comma = strchr(st, ','))) { l = strdup(st); if (!l) return (FALSE); c = strchr(l, ','); *c = '\0'; if (!cmp_renew_opts(l, &log_success, &log_failure)) { free(l); /* badent, skip */ return (TRUE); } free(l); l = NULL; st = comma; st++; } else { if (st) { if (!cmp_renew_opts(st, &log_success, &log_failure)) { /* badent, skip */ return (TRUE); } } break; } } /* while */ } else if (st) { /* we just have one opt */ if (!cmp_renew_opts(st, &log_success, &log_failure)) { /* badent, skip */ return (TRUE); } } /* if send_to is "renew", note it and refind send_to */ } else if (strncasecmp(send_to, "renew", sizeof ("renew")) == 0) { renew = 1; } if (kwarnd_debug) { printf("parseconf: renew=%d, log failure=%d, log success=%d\n", renew, log_failure, log_success); } if (renew) { /* find first non whitespace after send_to (start of exptime) */ for (send_to = ends+1; *send_to && isspace(*send_to); send_to++); /* if no send_to, continue, bad entry */ if (! *send_to) { if (kwarnd_debug) printf("parseconf: no send_to, badent, skip\n"); return (TRUE); } /* find end of send_to */ for (ends = send_to; *ends && !isspace(*ends); ends++); if (*ends) *ends = '\0'; } /* find first non whitespace after send_to (start of exptime) */ for (exptime = ends+1; *exptime && isspace(*exptime); exptime++); /* if no exptime, continue, bad entry */ if (! *exptime) { if (kwarnd_debug) printf("parseconf: no exptime, badent, skip\n"); return (TRUE); } /* find end of exptime */ for (ends = exptime; *ends && !isspace(*ends); ends++); tm = ends - 1; if (*tm == 's') time_mode = 1; else if (*tm == 'm') time_mode = 2; else if (*tm == 'h') time_mode = 3; else time_mode = 1; if (*tm) *tm = '\0'; if (kwarnd_debug) { printf("parseconf: send_to = '%s', exptime='%s'\n", send_to, exptime); } /* find first non whitespace after exptime (start of emailid) */ for (emailid = ends+1; *emailid && isspace(*emailid); emailid++); /* find end of emailid */ if (*emailid) { for (ends = emailid; *ends && !isspace(*ends); ends++); if (*ends) *ends = '\0'; } /* if send to mail and no mail address, bad entry */ if ((strcmp(send_to, "mail") == 0) && (!*emailid)) { if (kwarnd_debug) printf("parseconf: returns true; no mail addr\n"); syslog(LOG_ERR, gettext("missing mail address" " in config entry: \n%s %s %s " " cannot mail warning"), principal, send_to, exptime); return (TRUE); } /* create an entry */ config_entry = (config_entry_list_t *) malloc(sizeof (*config_entry_list)); if (config_entry == NULL) return (FALSE); (void) memset(config_entry, 0, sizeof (*config_entry_list)); config_entry->principal = strdup(principal); if (config_entry->principal == NULL) return (FALSE); config_entry->where_to = strdup(send_to); if (config_entry->where_to == NULL) return (FALSE); etime = atol(exptime); if (time_mode == 1) config_entry->seconds_to_warn = etime; else if (time_mode == 2) config_entry->seconds_to_warn = etime * 60; else if (time_mode == 3) config_entry->seconds_to_warn = etime * 60 * 60; if (*emailid) { config_entry->email = strdup(emailid); if (config_entry->email == NULL) return (FALSE); } config_entry->renew = renew; config_entry->log_success = log_success; config_entry->log_failure = log_failure; config_entry->next = config_entry_list; config_entry_list = config_entry; if (kwarnd_debug) printf("parseconf: returns true; celist=%p\n", config_entry_list); return (TRUE); } /* * find a specific warn.conf entry. */ static config_entry_list_t * find_warning_info(char *principal) { config_entry_list_t *config_entry; /* look for a specific entry */ for (config_entry = config_entry_list; config_entry; config_entry = config_entry->next) { if (strcmp(config_entry->principal, principal) == 0) { return (config_entry); } } /* look for a wild card entry */ for (config_entry = config_entry_list; config_entry; config_entry = config_entry->next) { if (strcmp(config_entry->principal, "*") == 0) { return (config_entry); } } /* nothing found */ return (NULL); } /* * create a pipe, fork and exec a command, */ static FILE * safe_popen_w(char *path_to_cmd, char **argv) { int fd[2]; FILE *fp; char *envp[2]; if (pipe(fd) == -1) return (NULL); switch (fork()) { case -1: (void) close(fd[0]); (void) close(fd[1]); return (NULL); case 0: close(fd[1]); /* fd[0] is the end we read from */ if (fd[0] != 0) { close(0); dup(fd[0]); } close(1); close(2); envp[0] = "PATH=/usr/bin"; envp[1] = NULL; #ifdef DEBUG { int fd; fd = open("/tmp/kwarn.out", O_WRONLY|O_TRUNC|O_CREAT, 0666); if (fd != 1) dup(fd); if (fd != 2) dup(fd); } #endif (void) execve(path_to_cmd, argv, envp); syslog(LOG_ERR, "warnd: %m"); _exit(1); default: close(fd[0]); /* fd[1] is the end we write to */ fp = fdopen(fd[1], "w"); if (fp == NULL) { (void) close(fd[1]); return (NULL); } return (fp); } } static uid_t gssd_uid; void set_warnd_uid(uid_t uid) { /* * set the value of gssd_uid, so it can be retrieved when getuid() * is called by the underlying mechanism libraries */ if (kwarnd_debug) printf("set_warnd_uid called with uid = %d\n", uid); gssd_uid = uid; } uid_t getuid(void) { /* * return the value set when one of the gssd procedures was * entered. This is the value of the uid under which the * underlying mechanism library must operate in order to * get the user's credentials. This call is necessary since * gssd runs as root and credentials are many times stored * in files and directories specific to the user */ if (kwarnd_debug) printf("getuid called and returning gsssd_uid = %d\n", gssd_uid); return (gssd_uid); } static bool_t getpruid(char *pr, uid_t *uid) { char *rcp1 = NULL, *rcp2 = NULL, *rcp3 = NULL; struct passwd *pw; rcp1 = strdup(pr); if (!rcp1) return (FALSE); rcp2 = strtok(rcp1, "@"); rcp3 = strtok(rcp2, "/"); if (rcp3) { pw = getpwnam(rcp3); *uid = pw->pw_uid; free(rcp1); return (TRUE); } free(rcp1); return (FALSE); } static krb5_error_code renew_creds( char *princ, time_t *new_exp_time) /* out */ { krb5_creds my_creds; krb5_error_code code = 0; struct k5_data k5; char *progname = "warnd"; uid_t saved_u = getuid(); uid_t u; if (kwarnd_debug) printf("renew start: uid=%d\n", getuid()); if (!getpruid(princ, &u)) { if (kwarnd_debug) printf("renew: getpruid failed, princ='%s'\n", princ ? princ : "<null>"); return (-1); /* better err num? */ } set_warnd_uid(u); (void) memset(&my_creds, 0, sizeof (my_creds)); (void) memset(&k5, 0, sizeof (k5)); if (code = krb5_init_context(&k5.ctx)) { com_err(progname, code, gettext("while initializing Kerberos 5 library")); goto out; } if ((code = krb5_cc_default(k5.ctx, &k5.cc))) { com_err(progname, code, gettext("while getting default ccache")); goto out; } if ((code = krb5_parse_name(k5.ctx, princ, &k5.me))) { com_err(progname, code, gettext("when parsing name %s"), princ); goto out; } if ((code = krb5_get_renewed_creds(k5.ctx, &my_creds, k5.me, k5.cc, NULL))) { com_err(progname, code, gettext("while renewing creds")); goto out; } if (code = krb5_cc_initialize(k5.ctx, k5.cc, k5.me)) { com_err(progname, code, gettext("when initializing cache %s"), "defcc"); goto out; } if (code = krb5_cc_store_cred(k5.ctx, k5.cc, &my_creds)) { com_err(progname, code, gettext("while storing credentials")); goto out; } /* "return" new expire time */ *new_exp_time = my_creds.times.endtime; out: krb5_free_cred_contents(k5.ctx, &my_creds); if (k5.name) krb5_free_unparsed_name(k5.ctx, k5.name); if (k5.me) krb5_free_principal(k5.ctx, k5.me); if (k5.cc) krb5_cc_close(k5.ctx, k5.cc); if (k5.ctx) krb5_free_context(k5.ctx); set_warnd_uid(saved_u); if (kwarnd_debug) printf("renew end: code=%s, uid=%d\n", error_message(code), getuid()); return (code); } static bool_t loggedon(char *name) { register struct utmpx *ubuf; char *rcp1 = NULL, *rcp2 = NULL, *rcp3 = NULL; /* * strip any realm or instance from principal so we can match * against unix userid. */ rcp1 = strdup(name); if (!rcp1) return (FALSE); rcp2 = strtok(rcp1, "@"); rcp3 = strtok(rcp2, "/"); /* * Scan through the "utmpx" file for the * entry for the person we want to send to. */ setutxent(); while ((ubuf = getutxent()) != NULL) { if (ubuf->ut_type == USER_PROCESS) { if (strncmp(rcp3, ubuf->ut_user, sizeof (ubuf->ut_user)) == 0) { free(rcp1); endutxent(); return (TRUE); } } } free(rcp1); endutxent(); if (kwarnd_debug) printf("loggedon: returning false for user `%s'\n", rcp1); return (FALSE); } /* * main loop to check the cred warning list and send the warnings * the appropriate location based on warn.conf or auto-renew creds. */ void kwarnd_check_warning_list(void) { /* func */ cred_warning_list_t *cw; /* cred warning */ config_entry_list_t *ce; /* config entry */ time_t now; int minutes; char buff[256]; char cmdline[256]; FILE *fp; char *subj = "Kerberos credentials expiring"; char *renew_subj = "Kerberos credentials renewed"; if (kwarnd_debug) printf("check list: start: getuid=%d, cw list=%p\n", getuid(), cred_warning_list); while (1) { (void) poll(NULL, NULL, 60000); for (cw = cred_warning_list; cw != NULL; cw = cw->next) { int send_msg = 0; time(&now); if (now >= cw->cred_warn_time) { int renew_attempted = 0; int renew_failed = 0; int renew_tooclose = 0; if (kwarnd_debug) printf("checklist: now >= warn_t\n"); ce = find_warning_info(cw->warn_name); minutes = (cw->cred_exp_time - now + 59) / 60; if (kwarnd_debug) printf("checklist: where_to=%s\n", ce->where_to ? ce->where_to : "null"); if (ce->renew && loggedon(cw->warn_name)) { krb5_error_code code; time_t new_exp_time; renew_attempted = 1; code = renew_creds( cw->warn_name, &new_exp_time); if (!code) { /* krb5 api renew success */ /* * So we had api success * but the new exp time * is same as current one * so we are too close * to Renewable_life time. */ if (cw->cred_exp_time == new_exp_time) { renew_tooclose = 1; if (kwarnd_debug) printf( "checklist: new expire time same as old expire time\n"); if (ce->log_failure) { send_msg = 1; snprintf(buff, sizeof (buff), gettext("%s:\r\nYour kerberos" " credentials have not been renewed" " (too close to Renewable_life).\r\n" "Please run kinit(1).\r\n"), cw->warn_name); } } else { /* update times */ cw->cred_exp_time = new_exp_time; cw->cred_warn_time = new_exp_time - ce->seconds_to_warn; } if (kwarnd_debug) printf( "check list: new_w_t=%d\n", cw->cred_warn_time); if (!renew_tooclose && ce->log_success) { if (kwarnd_debug) printf( "check list: log success\n"); send_msg = 1; snprintf(buff, sizeof (buff), gettext("%s:\r\nYour kerberos" " credentials have been renewed.\r\n"), cw->warn_name); } } /* !(code) */ if (!renew_tooclose && code && ce->log_failure) { if (kwarnd_debug) printf( "check list: log FAIL\n"); send_msg = 1; snprintf(buff, sizeof (buff), gettext("%s:\r\nYour kerberos" " credentials failed to be renewed (%s).\r\n"), cw->warn_name, error_message(code)); } renew_failed = code ? 1 : 0; } else if (minutes > 0) { send_msg = 1; snprintf(buff, sizeof (buff), gettext("%s:\r\nyour kerberos" " credentials expire in less than" " %d minutes.\r\n"), cw->warn_name, minutes); } else { send_msg = 1; snprintf(buff, sizeof (buff), gettext("%s:\r\nyour kerberos" " credentials have expired.\r\n"), cw->warn_name); } if (kwarnd_debug) printf("checklist: send_msg=%d\n", send_msg); if (!send_msg) goto del_warning; if (strncmp(ce->where_to, "mail", sizeof ("mail")) == 0) { char *argv[3]; argv[0] = MAIL; (void) snprintf(cmdline, sizeof (cmdline), "%s", ce->email); argv[1] = cmdline; argv[2] = NULL; fp = safe_popen_w(MAILPATH, argv); if (fp) { (void) fprintf(fp, "To: %s\nSubject: %s\n\n%s\n", ce->email, renew_attempted ? renew_subj : subj, buff); fclose(fp); } else { syslog(LOG_ERR, gettext("could not fork " "mail program to e-mail " "warning to %s\n"), cmdline); } } else if (strncmp(ce->where_to, "terminal", sizeof ("terminal")) == 0) { warn_send(cw->warn_name, buff); } else if (send_msg && strncmp(ce->where_to, "syslog", sizeof ("syslog")) == 0) { syslog(LOG_NOTICE|LOG_AUTH, "%s", buff); #if 0 } else if (strncmp(ce->where_to, "snmp", sizeof ("snmp")) == 0) { #endif } else { if (kwarnd_debug) printf( "unknown msg method=`%s'\n", ce->where_to); exit(1); } del_warning: if (!renew_attempted || renew_failed || renew_tooclose) { if (del_warning_pvt(cw->warn_name) == TRUE) { if (kwarnd_debug) printf( "check list: del warn succ\n"); break; } else { if (kwarnd_debug) printf( "could not delete warning\n"); syslog(LOG_ERR, gettext( "could not delete warning")); exit(1); } } } /* if (now) */ } /* for */ } /* while */ } /* func */