acmecert: Fix cryptography bugs.
[utils.git] / pam_krb5auto.c
1 /*
2  *  pam_krb5auto - Gets initial credentials non-interactively
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 <string.h>
23 #include <stdarg.h>
24 #include <malloc.h>
25 #include <syslog.h>
26 #include <krb5.h>
27 #include <pwd.h>
28 #include <errno.h>
29
30 #define PAM_SM_AUTH
31
32 #include <security/pam_modules.h>
33
34 #define DEF_INSTANCE "autologin"
35
36 struct options
37 {
38     char *realm;
39     char *instance;
40     char *keytab;
41     int debug;
42     int forwardable;
43     int renewable;
44 };
45
46 struct data
47 {
48     krb5_context ctx;
49     krb5_ccache cc;
50     krb5_principal me;
51     krb5_creds initcreds;
52     int hascreds;
53     uid_t uid;
54     gid_t gid;
55 };
56
57 static void log(int prio, char *format, ...)
58 {
59     va_list args;
60     char buf[1024];
61     
62     va_start(args, format);
63     snprintf(buf, sizeof(buf), "pam_krb5auto[%i]: %s", getpid(), format);
64     vsyslog(prio, buf, args);
65     va_end(args);
66 }
67
68 static struct options *parseopts(int argc, const char **argv)
69 {
70     int i;
71     struct options *opts;
72     const char *p;
73     int unit;
74     
75     opts = malloc(sizeof(*opts));
76     memset(opts, 0, sizeof(*opts));
77     for(i = 0; i < argc; i++) {
78         if(!strncmp(argv[i], "realm=", 6))
79             opts->realm = strdup(argv[i] + 6);
80         if(!strncmp(argv[i], "instance=", 9))
81             opts->instance = strdup(argv[i] + 9);
82         if(!strncmp(argv[i], "keytab=", 7))
83             opts->keytab = strdup(argv[i] + 7);
84         if(!strncmp(argv[i], "renew=", 6)) {
85             p = argv[i] + strlen(argv[i]) - 1;
86             unit = 1;
87             if((*p >= 'a') && (*p <= 'z')) {
88                 if(*p == 'm')
89                     unit = 60;
90                 else if(*p == 'h')
91                     unit = 3600;
92                 else if(*p == 'd')
93                     unit = 86400;
94                 else
95                     unit = 1;
96             }
97             opts->renewable = atoi(argv[i] + 6) * unit;
98         }
99         if(!strcmp(argv[i], "forwardable"))
100             opts->forwardable = 1;
101         if(!strcmp(argv[i], "debug"))
102             opts->debug = 1;
103     }
104     return(opts);
105 }
106
107 static void freeopts(struct options *opts)
108 {
109     if(opts->realm != NULL)
110         free(opts->realm);
111     if(opts->instance != NULL)
112         free(opts->instance);
113     if(opts->keytab != NULL)
114         free(opts->keytab);
115     free(opts);
116 }
117
118 static void freedata(struct data *data)
119 {
120     if(data->hascreds)
121         krb5_free_cred_contents(data->ctx, &data->initcreds);
122     if(data->cc != NULL)
123         krb5_cc_close(data->ctx, data->cc);
124     if(data->me != NULL)
125         krb5_free_principal(data->ctx, data->me);
126     if(data->ctx != NULL)
127         krb5_free_context(data->ctx);
128     free(data);
129 }
130
131 static void cleanupdata(pam_handle_t *pamh, struct data *data, int error_status)
132 {
133     freedata(data);
134 }
135
136 static struct data *getdata(pam_handle_t *pamh, struct options *opts)
137 {
138     int ret;
139     struct data *data;
140     char buf[1024];
141     const char *user, *instance;
142     struct passwd *pwent;
143     
144     data = NULL;
145     pam_get_data(pamh, "krb5auto-data", (const void **)&data);
146     if(data == NULL) {
147         if(opts->debug)
148             log(LOG_DEBUG, "creating new instance");
149         data = malloc(sizeof(*data));
150         memset(data, 0, sizeof(*data));
151         pam_get_user(pamh, &user, NULL);
152         if(user == NULL) {
153             log(LOG_ERR, "could not get user name");
154             freedata(data);
155             return(NULL);
156         }
157         errno = 0;
158         if((pwent = getpwnam(user)) == NULL) {
159             log(LOG_ERR, "could not user information for `%s': %s", user, (errno == 0)?"user not found":strerror(errno));
160             freedata(data);
161             return(NULL);
162         }
163         data->uid = pwent->pw_uid;
164         data->gid = pwent->pw_gid;
165         if((ret = krb5_init_context(&data->ctx)) != 0) {
166             log(LOG_CRIT, "could not create krb5 context: %s", error_message(ret));
167             freedata(data);
168             return(NULL);
169         }
170         if(opts->instance)
171             instance = opts->instance;
172         else
173             instance = DEF_INSTANCE;
174         if(opts->realm)
175             snprintf(buf, sizeof(buf), "%s/%s@%s", user, instance, opts->realm);
176         else
177             snprintf(buf, sizeof(buf), "%s/%s", user, instance);
178         if((ret = krb5_parse_name(data->ctx, buf, &data->me)) != 0) {
179             log(LOG_ERR, "could not parse principal name `%s': %s", buf, error_message(ret));
180             freedata(data);
181             return(NULL);
182         }
183         pam_set_data(pamh, "krb5auto-data", data, (void (*)(pam_handle_t *, void *, int))cleanupdata);
184     }
185     return(data);
186 }
187
188 static int savecreds(pam_handle_t *pamh, struct options *opts, struct data *data)
189 {
190     int ret, fd;
191     krb5_keytab kt;
192     krb5_get_init_creds_opt icopts;
193     char buf[1024], *ccname, *filename;
194     
195     krb5_get_init_creds_opt_init(&icopts);
196     kt = NULL;
197     
198     if(opts->keytab) {
199         if((ret = krb5_kt_resolve(data->ctx, opts->keytab, &kt)) != 0) {
200             log(LOG_ERR, "could not resolve keytab `%s': %s", opts->keytab, error_message(ret));
201             ret = PAM_SERVICE_ERR;
202             goto out;
203         }
204         if(opts->debug)
205             log(LOG_DEBUG, "using keytab `%s'", opts->keytab);
206     }
207     krb5_get_init_creds_opt_set_forwardable(&icopts, opts->forwardable);
208     krb5_get_init_creds_opt_set_renew_life(&icopts, opts->renewable);
209     if(data->hascreds) {
210         krb5_free_cred_contents(data->ctx, &data->initcreds);
211         data->hascreds = 0;
212     }
213     if((ret = krb5_get_init_creds_keytab(data->ctx, &data->initcreds, data->me, kt, 0, NULL, &icopts)) != 0) {
214         log(LOG_ERR, "could not get credentials: %s", error_message(ret));
215         ret = PAM_SERVICE_ERR;
216         goto out;
217     }
218     data->hascreds = 1;
219     if(opts->debug)
220         log(LOG_DEBUG, "got creds successfully");
221     snprintf(buf, sizeof(buf), "KRB5CCNAME=FILE:/tmp/krb5cc_%i_XXXXXX", data->uid);
222     ccname = buf + sizeof("KRB5CCNAME=") - 1;
223     filename = ccname + sizeof("FILE:") - 1;
224     if((fd = mkstemp(filename)) < 0) {
225         log(LOG_ERR, "could not create tempfile for credentials cache: %s", strerror(errno));
226         ret = PAM_SERVICE_ERR;
227         goto out;
228     }
229     close(fd);
230     if(opts->debug)
231         log(LOG_DEBUG, "created ccache `%s'", filename);
232     if((ret = krb5_cc_resolve(data->ctx, ccname, &data->cc)) != 0) {
233         log(LOG_ERR, "could not resolve ccache `%s': %s", ccname, error_message(ret));
234         unlink(filename);
235         ret = PAM_SERVICE_ERR;
236         goto out;
237     }
238     if((ret = krb5_cc_initialize(data->ctx, data->cc, data->me)) != 0) {
239         log(LOG_ERR, "could not initialize credentials cache `%s': %s", ccname, error_message(ret));
240         unlink(filename);
241         ret = PAM_SERVICE_ERR;
242         goto out;
243     }
244     if((ret = krb5_cc_store_cred(data->ctx, data->cc, &data->initcreds)) != 0) {
245         log(LOG_ERR, "could not store credentials: %s", error_message(ret));
246         unlink(filename);
247         ret = PAM_SERVICE_ERR;
248         goto out;
249     }
250     chown(filename, data->uid, data->gid);
251     pam_putenv(pamh, strdup(buf));
252     if(opts->debug)
253         log(LOG_DEBUG, "successfully initialized ccache");
254     ret = PAM_SUCCESS;
255     
256  out:
257     if(kt != NULL)
258         krb5_kt_close(data->ctx, kt);
259     return(ret);
260 }
261
262 static int delcreds(pam_handle_t *pamh, struct options *opts, struct data *data)
263 {
264     if(opts->debug)
265         log(LOG_DEBUG, "deleting credentials");
266     if(data->hascreds) {
267         krb5_free_cred_contents(data->ctx, &data->initcreds);
268         data->hascreds = 0;
269         if(opts->debug)
270             log(LOG_DEBUG, "freed internal creds");
271     }
272     if(data->cc != NULL) {
273         krb5_cc_destroy(data->ctx, data->cc);
274         data->cc = NULL;
275         if(opts->debug)
276             log(LOG_DEBUG, "destroyed ccache");
277     }
278     return(PAM_SUCCESS);
279 }
280
281 PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
282 {
283     struct options *opts;
284     
285     opts = parseopts(argc, argv);
286     if(opts->debug)
287         log(LOG_DEBUG, "pam_sm_authenticate called");
288     freeopts(opts);
289     return(PAM_IGNORE);
290 }
291
292 PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
293 {
294     struct options *opts;
295     struct data *data;
296     int ret;
297     
298     opts = parseopts(argc, argv);
299     if(opts->debug)
300         log(LOG_DEBUG, "pam_sm_setcred called");
301     data = getdata(pamh, opts);
302     if(data == NULL) {
303         log(LOG_ERR, "could not get data, erroring out");
304         return(PAM_SERVICE_ERR);
305     }
306     ret = PAM_SERVICE_ERR;
307     if(flags & PAM_ESTABLISH_CRED) {
308         ret = savecreds(pamh, opts, data);
309     } else if(flags & PAM_DELETE_CRED) {
310         ret = delcreds(pamh, opts, data);
311     }
312     freeopts(opts);
313     return(ret);
314 }
315
316 /*
317  * Local Variables:
318  * compile-command: "gcc -Wall -g --shared -fPIC -o pam_krb5auto.so pam_krb5auto.c -lkrb5"
319  * End:
320  */