4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
22 * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
27 * RPC server procedures for the usermode daemon kwarnd.
36 #include <sys/param.h>
37 #include <sys/syslog.h>
49 #include <profile/prof_int.h>
54 extern char progname
[];
66 #define MAILPATH "/usr/bin/mail"
67 #define DEFAULT_CONFIG "* terminal 30m"
68 #define CONF_FILENAME "/etc/krb5/warn.conf"
72 typedef struct config_entry_s
{
73 struct config_entry_s
*next
;
81 } config_entry_list_t
;
82 static config_entry_list_t
*config_entry_list
;
84 /* list of principals to be warned */
86 typedef struct cred_warning_list_s
{
87 struct cred_warning_list_s
*next
;
88 WARNING_NAME_T warn_name
;
90 time_t cred_warn_time
;
92 } cred_warning_list_t
;
93 static cred_warning_list_t
*cred_warning_list
;
94 static rwlock_t cred_lock
= DEFAULTRWLOCK
;
97 del_warning_pvt(char *);
99 static config_entry_list_t
*
100 find_warning_info(char *);
103 parseConfigLine(char *buffer
);
105 extern int warn_send(char *, char *);
107 extern int kwarnd_debug
;
109 cred_warning_list_t
*
110 find_cred_warning(WARNING_NAME_T warn_name
)
112 cred_warning_list_t
*cw
;
113 if (!cred_warning_list
)
115 for (cw
= cred_warning_list
; cw
!= NULL
; cw
= cw
->next
) {
116 if (strcmp(warn_name
, cw
->warn_name
) != 0)
124 * add a principal to the principal warning list
128 kwarn_add_warning_1_svc(kwarn_add_warning_arg
*argp
,
129 kwarn_add_warning_res
*res
,
130 struct svc_req
*rqstp
)
132 cred_warning_list_t
*cred_warning
;
133 config_entry_list_t
*config_entry
;
136 printf("kwarn_add_warning_1_svc start; cWlist=%p\n",
139 printf("kwarn_add_warning_1_svc: principal %s",
141 printf(" exp time: %d\n", argp
->cred_exp_time
);
145 * if there is no entry in the config file that matches the principal to
146 * be added to the warning list, return true because we are not going to
147 * send a warning for this principal.
150 if ((config_entry
= find_warning_info(argp
->warning_name
)) == NULL
) {
153 "kwarn_add_warning_1_svc find_warn_info: fails, cWlist=%p\n",
160 * see if a warning has already been created for this principal, if so
161 * update the warning time.
164 rw_wrlock(&cred_lock
);
165 if (cred_warning
= find_cred_warning(argp
->warning_name
)) {
166 rw_unlock(&cred_lock
);
167 mutex_lock(&cred_warning
->cwm
);
168 cred_warning
->cred_exp_time
= argp
->cred_exp_time
;
169 cred_warning
->cred_warn_time
= argp
->cred_exp_time
170 - config_entry
->seconds_to_warn
;
171 mutex_unlock(&cred_warning
->cwm
);
173 cred_warning
= (cred_warning_list_t
*)malloc(
174 sizeof (*cred_warning_list
));
175 if (cred_warning
== NULL
) {
176 rw_unlock(&cred_lock
);
180 (void) memset((char *)cred_warning
, 0,
181 sizeof (*cred_warning_list
));
182 cred_warning
->cred_exp_time
= argp
->cred_exp_time
;
183 cred_warning
->cred_warn_time
= argp
->cred_exp_time
184 - config_entry
->seconds_to_warn
;
185 cred_warning
->warn_name
= strdup(argp
->warning_name
);
186 if (cred_warning
->warn_name
== NULL
) {
188 rw_unlock(&cred_lock
);
192 mutex_init(&cred_warning
->cwm
, USYNC_THREAD
, NULL
);
193 cred_warning
->next
= cred_warning_list
;
194 cred_warning_list
= cred_warning
;
195 rw_unlock(&cred_lock
);
201 "kwarn_add_warning_1_svc end: returns true; cWlist=%p\n",
208 * delete a warning request for a given principal
212 kwarn_del_warning_1_svc(kwarn_del_warning_arg
*argp
,
213 kwarn_del_warning_res
*res
,
214 struct svc_req
*rqstp
)
217 printf(gettext("delete principal %s requested\n"),
220 if (del_warning_pvt(argp
->warning_name
) == TRUE
) {
224 printf(gettext("delete principal %s completed\n"),
232 printf(gettext("delete principal %s failed\n"),
240 del_warning_pvt(char *warning_name
)
242 cred_warning_list_t
*cred_warning
, *prev
;
243 rw_wrlock(&cred_lock
);
244 for (prev
= NULL
, cred_warning
= cred_warning_list
;
245 cred_warning
!= NULL
; prev
= cred_warning
,
246 cred_warning
= cred_warning
->next
) {
247 if (strcmp(cred_warning
->warn_name
, warning_name
) == 0) {
249 cred_warning_list
= cred_warning
->next
;
251 prev
->next
= cred_warning
->next
;
253 free(cred_warning
->warn_name
);
255 rw_unlock(&cred_lock
);
259 rw_unlock(&cred_lock
);
264 * load the warn.conf file into the config_entry list.
272 bool_t retval
= TRUE
;
274 if ((cfgfile
= fopen(CONF_FILENAME
, "r")) == NULL
) {
275 syslog(LOG_ERR
, gettext(
276 "could not open config file \"%s\"\n"),
278 syslog(LOG_ERR
, gettext(
279 "using default options \"%s\"\n"),
281 retval
= parseConfigLine(DEFAULT_CONFIG
);
283 (void) memset(buffer
, 0, sizeof (buffer
));
284 while ((fgets(buffer
, BUFSIZ
, cfgfile
) != NULL
) &&
286 retval
= parseConfigLine(buffer
);
293 * Return TRUE if we get a valid opt and update flags appro.
296 cmp_renew_opts(char *opt
,
297 int *log_success
, /* out */
298 int *log_failure
) /* out */
301 if (strncasecmp(opt
, "log",
302 sizeof ("log")) == 0) {
303 *log_success
= *log_failure
= 1;
304 } else if (strncasecmp(opt
, "log-success",
305 sizeof ("log-success")) == 0) {
307 } else if (strncasecmp(opt
, "log-failure",
308 sizeof ("log-failure")) == 0) {
312 printf("cmp_renew_opts: renew bad opt=`%s'\n",
321 * Make the config_entry item for the config_entry_list, based on
322 * buffer. The formats are
324 * <principal> [renew[:<opt1,...optN>]] syslog|terminal <time>
325 * <principal> [renew[:<opt1,...optN>]] mail <time> <e-mail address>
327 * where renew opts will be:
330 * - Log the result of the renew attempt on success using
331 * the specified method (syslog|terminal|mail)
334 * - Log the result of the renew attempt on failure using
335 * the specified method (syslog|terminal|mail)
338 * - Same as specifing both log-failure and log-success
340 * Note if no log options are given, there will be no logging.
345 parseConfigLine(char *buffer
)
347 char *principal
, *send_to
, *emailid
, *ends
, *tm
;
351 config_entry_list_t
*config_entry
;
356 /* ignore comments */
361 printf("parseconf: buffer=%s", buffer
);
363 /* find end of principal */
365 for (send_to
= buffer
; *send_to
&& !isspace(*send_to
);
368 /* find first non whitespace after principal (start of send_to) */
372 while (*send_to
&& isspace(*send_to
))
376 /* if no send_to, continue, bad entry */
380 /* find end of send_to */
381 for (ends
= send_to
; *ends
&& !isspace(*ends
);
387 if (strchr(send_to
, ':')) {
388 /* we've got renew opts */
389 char *st
= NULL
, *op
= NULL
;
391 op
= strdup(send_to
);
394 st
= strchr(op
, ':');
397 if (strncasecmp(op
, "renew", sizeof ("renew")) == 0) {
401 /* got a ':' but not preceeded w/renew, badent, skip */
403 printf("parseconf: colon badent, skip\n");
410 if (!st
|| !*st
|| isspace(*st
)) {
412 printf("parseconf: st badent, skip\n");
416 if (renew
&& strchr(st
, ',')) {
418 /* loop thru comma seperated list-o-opts */
419 char *comma
= NULL
, *c
= NULL
, *l
= NULL
;
421 if (st
&& (comma
= strchr(st
, ','))) {
427 if (!cmp_renew_opts(l
, &log_success
,
440 if (!cmp_renew_opts(st
,
451 /* we just have one opt */
452 if (!cmp_renew_opts(st
, &log_success
, &log_failure
)) {
458 /* if send_to is "renew", note it and refind send_to */
459 } else if (strncasecmp(send_to
, "renew",
460 sizeof ("renew")) == 0) {
466 printf("parseconf: renew=%d, log failure=%d, log success=%d\n",
467 renew
, log_failure
, log_success
);
471 /* find first non whitespace after send_to (start of exptime) */
472 for (send_to
= ends
+1; *send_to
&& isspace(*send_to
);
475 /* if no send_to, continue, bad entry */
478 printf("parseconf: no send_to, badent, skip\n");
482 /* find end of send_to */
483 for (ends
= send_to
; *ends
&& !isspace(*ends
);
490 /* find first non whitespace after send_to (start of exptime) */
491 for (exptime
= ends
+1; *exptime
&& isspace(*exptime
);
494 /* if no exptime, continue, bad entry */
497 printf("parseconf: no exptime, badent, skip\n");
501 /* find end of exptime */
502 for (ends
= exptime
; *ends
&& !isspace(*ends
); ends
++);
518 printf("parseconf: send_to = '%s', exptime='%s'\n",
522 /* find first non whitespace after exptime (start of emailid) */
523 for (emailid
= ends
+1; *emailid
&& isspace(*emailid
); emailid
++);
525 /* find end of emailid */
527 for (ends
= emailid
; *ends
&& !isspace(*ends
);
534 /* if send to mail and no mail address, bad entry */
535 if ((strcmp(send_to
, "mail") == 0) && (!*emailid
)) {
537 printf("parseconf: returns true; no mail addr\n");
539 syslog(LOG_ERR
, gettext("missing mail address"
540 " in config entry: \n%s %s %s "
541 " cannot mail warning"), principal
,
546 /* create an entry */
547 config_entry
= (config_entry_list_t
*)
548 malloc(sizeof (*config_entry_list
));
549 if (config_entry
== NULL
)
551 (void) memset(config_entry
, 0, sizeof (*config_entry_list
));
552 config_entry
->principal
= strdup(principal
);
553 if (config_entry
->principal
== NULL
)
555 config_entry
->where_to
= strdup(send_to
);
556 if (config_entry
->where_to
== NULL
)
558 etime
= atol(exptime
);
560 config_entry
->seconds_to_warn
= etime
;
561 else if (time_mode
== 2)
562 config_entry
->seconds_to_warn
= etime
* 60;
563 else if (time_mode
== 3)
564 config_entry
->seconds_to_warn
= etime
* 60 * 60;
567 config_entry
->email
= strdup(emailid
);
568 if (config_entry
->email
== NULL
)
572 config_entry
->renew
= renew
;
573 config_entry
->log_success
= log_success
;
574 config_entry
->log_failure
= log_failure
;
575 config_entry
->next
= config_entry_list
;
576 config_entry_list
= config_entry
;
578 printf("parseconf: returns true; celist=%p\n",
585 * find a specific warn.conf entry.
588 static config_entry_list_t
*
589 find_warning_info(char *principal
)
591 config_entry_list_t
*config_entry
;
592 /* look for a specific entry */
593 for (config_entry
= config_entry_list
; config_entry
;
594 config_entry
= config_entry
->next
) {
595 if (strcmp(config_entry
->principal
, principal
) == 0) {
596 return (config_entry
);
599 /* look for a wild card entry */
600 for (config_entry
= config_entry_list
; config_entry
;
601 config_entry
= config_entry
->next
) {
602 if (strcmp(config_entry
->principal
, "*") == 0) {
603 return (config_entry
);
612 * create a pipe, fork and exec a command,
615 safe_popen_w(char *path_to_cmd
, char **argv
)
634 /* fd[0] is the end we read from */
641 envp
[0] = "PATH=/usr/bin";
646 fd
= open("/tmp/kwarn.out", O_WRONLY
|O_TRUNC
|O_CREAT
,
654 (void) execve(path_to_cmd
, argv
, envp
);
655 syslog(LOG_ERR
, "warnd: %m");
660 /* fd[1] is the end we write to */
662 fp
= fdopen(fd
[1], "w");
673 static uid_t krb5_cc_uid
;
676 set_warnd_uid(uid_t uid
)
679 * set the value of krb5_cc_uid, so it can be retrieved when
680 * app_krb5_user_uid() is called by the underlying mechanism libraries.
683 printf("set_warnd_uid called with uid = %d\n", uid
);
688 app_krb5_user_uid(void)
692 * return the value set when one of the kwarnd procedures was
693 * entered. This is the value of the uid under which the
694 * underlying mechanism library must operate in order to
695 * get the user's credentials. This call is necessary since
696 * kwarnd runs as root and credentials are many times stored
697 * in files and directories specific to the user
700 printf("app_krb5_user_uid called and returning uid = %d\n",
702 return (krb5_cc_uid
);
707 getpruid(char *pr
, uid_t
*uid
)
709 char *rcp1
= NULL
, *rcp2
= NULL
, *rcp3
= NULL
;
715 rcp2
= strtok(rcp1
, "@");
716 rcp3
= strtok(rcp2
, "/");
730 static krb5_error_code
733 time_t *new_exp_time
) /* out */
736 krb5_error_code code
= 0;
739 uid_t saved_u
= app_krb5_user_uid();
743 printf("renew start: uid=%d\n", app_krb5_user_uid());
745 if (!getpruid(princ
, &u
)) {
747 printf("renew: getpruid failed, princ='%s'\n",
748 princ
? princ
: "<null>");
750 return (-1); /* better err num? */
755 (void) memset(&my_creds
, 0, sizeof (my_creds
));
756 (void) memset(&k5
, 0, sizeof (k5
));
758 if (code
= krb5_init_context(&k5
.ctx
)) {
759 com_err(progname
, code
,
760 gettext("while initializing Kerberos 5 library"));
764 if ((code
= krb5_cc_default(k5
.ctx
, &k5
.cc
))) {
765 com_err(progname
, code
,
766 gettext("while getting default ccache"));
771 if ((code
= krb5_parse_name(k5
.ctx
, princ
,
773 com_err(progname
, code
, gettext("when parsing name %s"),
778 if ((code
= krb5_get_renewed_creds(k5
.ctx
, &my_creds
, k5
.me
, k5
.cc
,
780 com_err(progname
, code
, gettext("while renewing creds"));
784 if (code
= krb5_cc_initialize(k5
.ctx
, k5
.cc
, k5
.me
)) {
785 com_err(progname
, code
, gettext("when initializing cache %s"),
790 if (code
= krb5_cc_store_cred(k5
.ctx
, k5
.cc
, &my_creds
)) {
791 com_err(progname
, code
, gettext("while storing credentials"));
795 /* "return" new expire time */
796 *new_exp_time
= my_creds
.times
.endtime
;
799 krb5_free_cred_contents(k5
.ctx
, &my_creds
);
802 krb5_free_unparsed_name(k5
.ctx
, k5
.name
);
804 krb5_free_principal(k5
.ctx
, k5
.me
);
806 krb5_cc_close(k5
.ctx
, k5
.cc
);
808 krb5_free_context(k5
.ctx
);
810 set_warnd_uid(saved_u
);
813 printf("renew end: code=%s, uid=%d\n", error_message(code
),
814 app_krb5_user_uid());
822 register struct utmpx
*ubuf
;
823 char *rcp1
= NULL
, *rcp2
= NULL
, *rcp3
= NULL
;
826 * strip any realm or instance from principal so we can match
827 * against unix userid.
832 rcp2
= strtok(rcp1
, "@");
833 rcp3
= strtok(rcp2
, "/");
836 * Scan through the "utmpx" file for the
837 * entry for the person we want to send to.
841 while ((ubuf
= getutxent()) != NULL
) {
842 if (ubuf
->ut_type
== USER_PROCESS
) {
843 if (strncmp(rcp3
, ubuf
->ut_user
,
844 sizeof (ubuf
->ut_user
)) == 0) {
856 printf("loggedon: returning false for user `%s'\n", rcp1
);
862 * main loop to check the cred warning list and send the warnings
863 * the appropriate location based on warn.conf or auto-renew creds.
867 kwarnd_check_warning_list(void)
869 cred_warning_list_t
*cw
; /* cred warning */
870 config_entry_list_t
*ce
; /* config entry */
876 char *subj
= "Kerberos credentials expiring";
877 char *renew_subj
= "Kerberos credentials renewed";
880 printf("check list: start: uid=%d, cw list=%p\n",
881 app_krb5_user_uid(), cred_warning_list
);
884 (void) poll(NULL
, 0, 60000);
886 for (cw
= cred_warning_list
;
892 if (now
>= cw
->cred_warn_time
) {
893 int renew_attempted
= 0;
894 int renew_failed
= 0;
895 int renew_tooclose
= 0;
898 printf("checklist: now >= warn_t\n");
900 ce
= find_warning_info(cw
->warn_name
);
901 minutes
= (cw
->cred_exp_time
-
905 printf("checklist: where_to=%s\n",
907 ce
->where_to
: "null");
910 loggedon(cw
->warn_name
)) {
911 krb5_error_code code
;
919 /* krb5 api renew success */
922 * So we had api success
923 * but the new exp time
924 * is same as current one
925 * so we are too close
926 * to Renewable_life time.
928 if (cw
->cred_exp_time
933 "checklist: new expire time same as old expire time\n");
935 if (ce
->log_failure
) {
939 gettext("%s:\r\nYour kerberos"
940 " credentials have not been renewed"
941 " (too close to Renewable_life).\r\n"
942 "Please run kinit(1).\r\n"),
956 "check list: new_w_t=%d\n",
959 if (!renew_tooclose
&&
963 "check list: log success\n");
968 gettext("%s:\r\nYour kerberos"
969 " credentials have been renewed.\r\n"),
975 if (!renew_tooclose
&& code
&&
979 "check list: log FAIL\n");
984 gettext("%s:\r\nYour kerberos"
985 " credentials failed to be renewed (%s).\r\n"),
987 error_message(code
));
989 renew_failed
= code
? 1 : 0;
991 } else if (minutes
> 0) {
993 snprintf(buff
, sizeof (buff
),
994 gettext("%s:\r\nyour kerberos"
995 " credentials expire in less than"
1001 snprintf(buff
, sizeof (buff
),
1002 gettext("%s:\r\nyour kerberos"
1003 " credentials have expired.\r\n"),
1008 printf("checklist: send_msg=%d\n",
1013 if (strncmp(ce
->where_to
,
1014 "mail", sizeof ("mail")) == 0) {
1018 (void) snprintf(cmdline
,
1025 fp
= safe_popen_w(MAILPATH
, argv
);
1030 "To: %s\nSubject: %s\n\n%s\n",
1033 ? renew_subj
: subj
,
1039 gettext("could not fork "
1040 "mail program to e-mail "
1045 } else if (strncmp(ce
->where_to
,
1047 sizeof ("terminal")) == 0) {
1049 warn_send(cw
->warn_name
,
1052 } else if (send_msg
&& strncmp(ce
->where_to
,
1054 sizeof ("syslog")) == 0) {
1055 syslog(LOG_NOTICE
|LOG_AUTH
,
1059 } else if (strncmp(ce
->where_to
,
1061 sizeof ("snmp")) == 0) {
1066 "unknown msg method=`%s'\n",
1073 if (!renew_attempted
|| renew_failed
||
1075 if (del_warning_pvt(cw
->warn_name
)
1080 "check list: del warn succ\n");
1086 "could not delete warning\n");
1088 syslog(LOG_ERR
, gettext(
1089 "could not delete warning"));