ann.py: Further improvements.
[utils.git] / krb5-agent.c
1 /*
2  *  krb5-agent - Renews Kerberos tickets periodically.
3  *  Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
4  *  
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *  
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *  
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 */
19 #include <stdlib.h>
20 #include <stdio.h>
21 #include <unistd.h>
22 #include <krb5.h>
23 #include <time.h>
24 #include <string.h>
25 #include <signal.h>
26 #include <sys/wait.h>
27
28 volatile int died = 0;
29 pid_t child;
30 int execmode = 0, failsafe = 0;
31 int verbose = 0, quiet = 0;
32 time_t renewat;
33
34 krb5_context context = NULL;
35 krb5_ccache ccache = NULL;
36
37 void cleanup_krb5(void)
38 {
39     if(ccache != NULL)
40         krb5_cc_close(context, ccache);
41     ccache = NULL;
42     if(context != NULL)
43         krb5_free_context(context);
44     context = NULL;
45 }
46
47 void sighandler(int sig)
48 {
49     switch(sig) {
50     case SIGCHLD:
51         died = 1;
52         break;
53     case SIGHUP:
54     case SIGINT:
55     case SIGTERM:
56         if(execmode)
57             kill(child, sig);
58         break;
59     }
60 }
61
62 void renew(void)
63 {
64     int ret;
65     krb5_principal me;
66     krb5_creds creds;
67     
68     if(context == NULL)
69         return;
70     if(ccache == NULL) {
71         if((ret = krb5_cc_default(context, &ccache)) != 0) {
72             if(!quiet)
73                 fprintf(stderr, "could not initialize Kerberos context: %s\n", error_message(ret));
74             return;
75         }
76     }
77     
78     me = NULL;
79     memset(&creds, 0, sizeof(creds));
80     if((ret = krb5_cc_get_principal(context, ccache, &me)) != 0) {
81         if(!quiet)
82             fprintf(stderr, "could not get principal from cache: %s\n", error_message(ret));
83         goto out;
84     }
85     if((ret = krb5_get_renewed_creds(context, &creds, me, ccache, NULL)) != 0) {
86         if(!quiet)
87             fprintf(stderr, "could not get renewed credentials: %s\n", error_message(ret));
88         goto out;
89     }
90     if((ret = krb5_cc_initialize(context, ccache, me)) != 0) {
91         if(!quiet)
92             fprintf(stderr, "could not reinitialize cache: %s\n", error_message(ret));
93         krb5_free_cred_contents(context, &creds);
94         goto out;
95     }
96     if((ret = krb5_cc_store_cred(context, ccache, &creds)) != 0) {
97         if(!quiet)
98             fprintf(stderr, "could not store renewed credentials: %s\n", error_message(ret));
99         krb5_free_cred_contents(context, &creds);
100         goto out;
101     }
102     krb5_free_cred_contents(context, &creds);
103     if(verbose >= 1)
104         printf("successfully renewed credentials\n");
105     
106  out:
107     if(me != NULL)
108         krb5_free_principal(context, me);
109 }
110
111 time_t goodrenewtime(void)
112 {
113     int ret;
114     krb5_principal me;
115     krb5_cc_cursor cur;
116     krb5_creds creds;
117     time_t now, good;
118     
119     if(context == NULL)
120         return(-1);
121     if(ccache == NULL) {
122         if((ret = krb5_cc_default(context, &ccache)) != 0) {
123             if(!quiet)
124                 fprintf(stderr, "could not initialize Kerberos context: %s\n", error_message(ret));
125             return(-1);
126         }
127     }
128     
129     me = NULL;
130     cur = NULL;
131     good = -1;
132     if((ret = krb5_cc_get_principal(context, ccache, &me)) != 0) {
133         if(!quiet)
134             fprintf(stderr, "could not get principal from cache: %s\n", error_message(ret));
135         goto out;
136     }
137     if((ret = krb5_cc_start_seq_get(context, ccache, &cur)) != 0) {
138         if(!quiet)
139             fprintf(stderr, "could not open credentials cache: %s\n", error_message(ret));
140         goto out;
141     }
142     now = time(NULL);
143     while(!krb5_cc_next_cred(context, ccache, &cur, &creds)) {
144         if(!strcmp(krb5_princ_component(context, creds.server, 0)->data, KRB5_TGS_NAME) &&
145            !strcmp(krb5_princ_component(context, creds.server, 1)->data, me->realm.data)) {
146             if(!creds.times.starttime)
147                 creds.times.starttime = creds.times.authtime;
148             good = (creds.times.starttime + (((creds.times.endtime - creds.times.starttime) * 9) / 10));
149             break;
150         }
151         krb5_free_cred_contents(context, &creds);
152     }
153     
154  out:
155     if(cur != NULL)
156         krb5_cc_end_seq_get(context, ccache, &cur);
157     if(me != NULL)
158         krb5_free_principal(context, me);
159     return(good);
160 }
161
162 int main(int argc, char **argv)
163 {
164     char *p;
165     int c;
166     time_t interval, now;
167     pid_t wpid;
168     int ret, status;
169     
170     interval = -1;
171     while((c = getopt(argc, argv, "+hi:vqf")) != -1) {
172         switch(c) {
173         case 'v':
174             verbose++;
175             break;
176         case 'q':
177             quiet = 1;
178             break;
179         case 'f':
180             failsafe = 1;
181             break;
182         case 'i':
183             p = optarg + strlen(optarg) - 1;
184             if((*p >= 'a') && (*p <= 'z')) {
185                 if(*p == 'm')
186                     interval = 60;
187                 else if(*p == 'h')
188                     interval = 3600;
189                 else if(*p == 'd')
190                     interval = 86400;
191                 else
192                     interval = 1;
193                 *p = 0;
194             } else {
195                 interval = 1;
196             }
197             interval *= atoi(optarg);
198             break;
199         case 'h':
200         case '?':
201         case ':':
202         default:
203             fprintf(stderr, "usage: krb5-agent [-hvqf] [-i interval] [program args...]\n");
204             exit((c == 'h')?0:1);
205         }
206     }
207     
208     atexit(cleanup_krb5);
209     if((ret = krb5_init_context(&context)) != 0) {
210         if(!quiet)
211             fprintf(stderr, "could not initialize Kerberos context: %s\n", error_message(ret));
212         if(!failsafe)
213             exit(1);
214     }
215     if(context != NULL) {
216         if((ret = krb5_cc_default(context, &ccache)) != 0) {
217             if(!quiet)
218                 fprintf(stderr, "could not initialize Kerberos context: %s\n", error_message(ret));
219             if(!failsafe)
220                 exit(1);
221         }
222     }
223     
224     if(optind < argc) {
225         execmode = 1;
226         signal(SIGCHLD, sighandler);
227         if((child = fork()) < 0) {
228             perror("fork");
229             exit(1);
230         }
231         if(child == 0) {
232             char buf[80];
233             snprintf(buf, 80, "KRB5_AGENT_PID=%i", getppid());
234             putenv(buf);
235             execvp(argv[optind], argv + optind);
236             perror(argv[optind]);
237             exit(255);
238         }
239         signal(SIGHUP, sighandler);
240         signal(SIGINT, sighandler);
241         signal(SIGTERM, sighandler);
242     }
243     now = time(NULL);
244     if(interval >= 0)
245         renewat = now + interval;
246     while(1) {
247         if(died) {
248             wpid = waitpid(-1, &status, WNOHANG);
249             if(wpid < 0) {
250                 perror("waitpid");
251             } else if(execmode && (wpid == child)) {
252                 /* Try to preserve exit status as best as we can... */
253                 if(WIFEXITED(status)) {
254                     exit(WEXITSTATUS(status));
255                 } else {
256                     cleanup_krb5();
257                     signal(WTERMSIG(status), SIG_DFL);
258                     kill(getpid(), WTERMSIG(status));
259                     exit(255);
260                 }
261             }
262             died = 0;
263         }
264         if(interval < 0) {
265             if((renewat = goodrenewtime()) < 0) {
266                 renewat = -1;
267             } else if(verbose >= 2) {
268                 printf("will renew tickets at %s", ctime(&renewat));
269             }
270         }
271         if(renewat < 0)
272             sleep(60);
273         else
274             sleep(renewat - now);
275         now = time(NULL);
276         if((renewat >= 0) && (now >= renewat)) {
277             renew();
278             now = time(NULL);
279             if(interval >= 0)
280                 renewat = now + interval;
281         }
282     }
283 }
284
285 /*
286  * Local Variables:
287  * compile-command: "gcc -Wall -g -o krb5-agent krb5-agent.c -lkrb5"
288  * End:
289  */