xref: /freebsd/crypto/heimdal/lib/krb5/fcache.c (revision 5521ff5a4d1929056e7ffc982fac3341ca54df7c)
1 /*
2  * Copyright (c) 1997 - 2001 Kungliga Tekniska H�gskolan
3  * (Royal Institute of Technology, Stockholm, Sweden).
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  *
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * 3. Neither the name of the Institute nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33 
34 #include "krb5_locl.h"
35 
36 RCSID("$Id: fcache.c,v 1.33 2001/05/14 06:14:46 assar Exp $");
37 
38 typedef struct krb5_fcache{
39     char *filename;
40     int version;
41 }krb5_fcache;
42 
43 struct fcc_cursor {
44     int fd;
45     krb5_storage *sp;
46 };
47 
48 #define KRB5_FCC_FVNO_1 1
49 #define KRB5_FCC_FVNO_2 2
50 #define KRB5_FCC_FVNO_3 3
51 #define KRB5_FCC_FVNO_4 4
52 
53 #define FCC_TAG_DELTATIME 1
54 
55 #define FCACHE(X) ((krb5_fcache*)(X)->data.data)
56 
57 #define FILENAME(X) (FCACHE(X)->filename)
58 
59 #define FCC_CURSOR(C) ((struct fcc_cursor*)(C))
60 
61 static char*
62 fcc_get_name(krb5_context context,
63 	     krb5_ccache id)
64 {
65     return FILENAME(id);
66 }
67 
68 static krb5_error_code
69 fcc_resolve(krb5_context context, krb5_ccache *id, const char *res)
70 {
71     krb5_fcache *f;
72     f = malloc(sizeof(*f));
73     if(f == NULL) {
74 	krb5_set_error_string(context, "malloc: out of memory");
75 	return KRB5_CC_NOMEM;
76     }
77     f->filename = strdup(res);
78     if(f->filename == NULL){
79 	free(f);
80 	krb5_set_error_string(context, "malloc: out of memory");
81 	return KRB5_CC_NOMEM;
82     }
83     f->version = 0;
84     (*id)->data.data = f;
85     (*id)->data.length = sizeof(*f);
86     return 0;
87 }
88 
89 /*
90  * Try to scrub the contents of `filename' safely.
91  */
92 
93 static int
94 scrub_file (int fd)
95 {
96     off_t pos;
97     char buf[128];
98 
99     pos = lseek(fd, 0, SEEK_END);
100     if (pos < 0)
101         return errno;
102     if (lseek(fd, 0, SEEK_SET) < 0)
103         return errno;
104     memset(buf, 0, sizeof(buf));
105     while(pos > 0) {
106         ssize_t tmp = write(fd, buf, min(sizeof(buf), pos));
107 
108 	if (tmp < 0)
109 	    return errno;
110 	pos -= tmp;
111     }
112     fsync (fd);
113     return 0;
114 }
115 
116 /*
117  * Erase `filename' if it exists, trying to remove the contents if
118  * it's `safe'.  We always try to remove the file, it it exists.  It's
119  * only overwritten if it's a regular file (not a symlink and not a
120  * hardlink)
121  */
122 
123 static krb5_error_code
124 erase_file(const char *filename)
125 {
126     int fd;
127     struct stat sb1, sb2;
128     int ret;
129 
130     ret = lstat (filename, &sb1);
131     if (ret < 0)
132 	return errno;
133 
134     fd = open(filename, O_RDWR | O_BINARY);
135     if(fd < 0) {
136 	if(errno == ENOENT)
137 	    return 0;
138 	else
139 	    return errno;
140     }
141     if (unlink(filename) < 0) {
142         close (fd);
143         return errno;
144     }
145 
146     ret = fstat (fd, &sb2);
147     if (ret < 0) {
148 	close (fd);
149 	return errno;
150     }
151 
152     /* check if someone was playing with symlinks */
153 
154     if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino) {
155 	close (fd);
156 	return EPERM;
157     }
158 
159     /* there are still hard links to this file */
160 
161     if (sb2.st_nlink != 0) {
162         close (fd);
163         return 0;
164     }
165 
166     ret = scrub_file (fd);
167     close (fd);
168     return ret;
169 }
170 
171 static krb5_error_code
172 fcc_gen_new(krb5_context context, krb5_ccache *id)
173 {
174     krb5_fcache *f;
175     int fd;
176     char *file;
177 
178     f = malloc(sizeof(*f));
179     if(f == NULL) {
180 	krb5_set_error_string(context, "malloc: out of memory");
181 	return KRB5_CC_NOMEM;
182     }
183     asprintf (&file, "%sXXXXXX", KRB5_DEFAULT_CCFILE_ROOT);
184     if(file == NULL) {
185 	free(f);
186 	krb5_set_error_string(context, "malloc: out of memory");
187 	return KRB5_CC_NOMEM;
188     }
189     fd = mkstemp(file);
190     if(fd < 0) {
191 	free(f);
192 	free(file);
193 	krb5_set_error_string(context, "mkstemp %s", file);
194 	return errno;
195     }
196     close(fd);
197     f->filename = file;
198     f->version = 0;
199     (*id)->data.data = f;
200     (*id)->data.length = sizeof(*f);
201     return 0;
202 }
203 
204 static void
205 storage_set_flags(krb5_context context, krb5_storage *sp, int vno)
206 {
207     int flags = 0;
208     switch(vno) {
209     case KRB5_FCC_FVNO_1:
210 	flags |= KRB5_STORAGE_PRINCIPAL_WRONG_NUM_COMPONENTS;
211 	flags |= KRB5_STORAGE_PRINCIPAL_NO_NAME_TYPE;
212 	flags |= KRB5_STORAGE_HOST_BYTEORDER;
213 	break;
214     case KRB5_FCC_FVNO_2:
215 	flags |= KRB5_STORAGE_HOST_BYTEORDER;
216 	break;
217     case KRB5_FCC_FVNO_3:
218 	flags |= KRB5_STORAGE_KEYBLOCK_KEYTYPE_TWICE;
219 	break;
220     case KRB5_FCC_FVNO_4:
221 	break;
222     default:
223 	krb5_abortx(context,
224 		    "storage_set_flags called with bad vno (%x)", vno);
225     }
226     krb5_storage_set_flags(sp, flags);
227 }
228 
229 static krb5_error_code
230 fcc_initialize(krb5_context context,
231 	       krb5_ccache id,
232 	       krb5_principal primary_principal)
233 {
234     krb5_fcache *f = FCACHE(id);
235     int ret = 0;
236     int fd;
237     char *filename = f->filename;
238 
239     unlink (filename);
240 
241     fd = open(filename, O_RDWR | O_CREAT | O_EXCL | O_BINARY, 0600);
242     if(fd == -1) {
243 	ret = errno;
244 	krb5_set_error_string(context, "open(%s): %s", filename,
245 			      strerror(ret));
246 	return ret;
247     }
248     {
249 	krb5_storage *sp;
250 	sp = krb5_storage_from_fd(fd);
251 	if(context->fcache_vno != 0)
252 	    f->version = context->fcache_vno;
253 	else
254 	    f->version = KRB5_FCC_FVNO_4;
255 	ret |= krb5_store_int8(sp, 5);
256 	ret |= krb5_store_int8(sp, f->version);
257 	storage_set_flags(context, sp, f->version);
258 	if(f->version == KRB5_FCC_FVNO_4 && ret == 0) {
259 	    /* V4 stuff */
260 	    if (context->kdc_sec_offset) {
261 		ret |= krb5_store_int16 (sp, 12); /* length */
262 		ret |= krb5_store_int16 (sp, FCC_TAG_DELTATIME); /* Tag */
263 		ret |= krb5_store_int16 (sp, 8); /* length of data */
264 		ret |= krb5_store_int32 (sp, context->kdc_sec_offset);
265 		ret |= krb5_store_int32 (sp, context->kdc_usec_offset);
266 	    } else {
267 		ret |= krb5_store_int16 (sp, 0);
268 	    }
269 	}
270 	ret |= krb5_store_principal(sp, primary_principal);
271 	krb5_storage_free(sp);
272     }
273     if(close(fd) < 0)
274 	if (ret == 0) {
275 	    ret = errno;
276 	    krb5_set_error_string (context, "close %s: %s", filename,
277 				   strerror(ret));
278 	}
279 
280     return ret;
281 }
282 
283 static krb5_error_code
284 fcc_close(krb5_context context,
285 	  krb5_ccache id)
286 {
287     free (FILENAME(id));
288     krb5_data_free(&id->data);
289     return 0;
290 }
291 
292 static krb5_error_code
293 fcc_destroy(krb5_context context,
294 	    krb5_ccache id)
295 {
296     char *f;
297     f = FILENAME(id);
298 
299     erase_file(f);
300 
301     return 0;
302 }
303 
304 static krb5_error_code
305 fcc_store_cred(krb5_context context,
306 	       krb5_ccache id,
307 	       krb5_creds *creds)
308 {
309     int ret;
310     int fd;
311     char *f;
312 
313     f = FILENAME(id);
314 
315     fd = open(f, O_WRONLY | O_APPEND | O_BINARY);
316     if(fd < 0) {
317 	ret = errno;
318 	krb5_set_error_string (context, "open(%s): %s", f, strerror(ret));
319 	return ret;
320     }
321     {
322 	krb5_storage *sp;
323 	sp = krb5_storage_from_fd(fd);
324 	storage_set_flags(context, sp, FCACHE(id)->version);
325 	ret = krb5_store_creds(sp, creds);
326 	krb5_storage_free(sp);
327     }
328     if (close(fd) < 0)
329 	if (ret == 0) {
330 	    ret = errno;
331 	    krb5_set_error_string (context, "close %s: %s", f, strerror(ret));
332 	}
333     return ret;
334 }
335 
336 static krb5_error_code
337 fcc_read_cred (krb5_context context,
338 	       krb5_fcache *fc,
339 	       krb5_storage *sp,
340 	       krb5_creds *creds)
341 {
342     krb5_error_code ret;
343 
344     storage_set_flags(context, sp, fc->version);
345 
346     ret = krb5_ret_creds(sp, creds);
347     return ret;
348 }
349 
350 static krb5_error_code
351 init_fcc (krb5_context context,
352 	  krb5_fcache *fcache,
353 	  krb5_storage **ret_sp,
354 	  int *ret_fd)
355 {
356     int fd;
357     int8_t pvno, tag;
358     krb5_storage *sp;
359     krb5_error_code ret;
360 
361     fd = open(fcache->filename, O_RDONLY | O_BINARY);
362     if(fd < 0) {
363 	ret = errno;
364 	krb5_set_error_string(context, "open(%s): %s", fcache->filename,
365 			      strerror(ret));
366 	return ret;
367     }
368     sp = krb5_storage_from_fd(fd);
369     ret = krb5_ret_int8(sp, &pvno);
370     if(ret == KRB5_CC_END) {
371 
372 	return ENOENT;
373     }
374     if(ret)
375 	return ret;
376     if(pvno != 5) {
377 	krb5_storage_free(sp);
378 	close(fd);
379 	return KRB5_CCACHE_BADVNO;
380     }
381     krb5_ret_int8(sp, &tag); /* should not be host byte order */
382     fcache->version = tag;
383     storage_set_flags(context, sp, fcache->version);
384     switch (tag) {
385     case KRB5_FCC_FVNO_4: {
386 	int16_t length;
387 
388 	krb5_ret_int16 (sp, &length);
389 	while(length > 0) {
390 	    int16_t tag, data_len;
391 	    int i;
392 	    int8_t dummy;
393 
394 	    krb5_ret_int16 (sp, &tag);
395 	    krb5_ret_int16 (sp, &data_len);
396 	    switch (tag) {
397 	    case FCC_TAG_DELTATIME :
398 		krb5_ret_int32 (sp, &context->kdc_sec_offset);
399 		krb5_ret_int32 (sp, &context->kdc_usec_offset);
400 		break;
401 	    default :
402 		for (i = 0; i < data_len; ++i)
403 		    krb5_ret_int8 (sp, &dummy);
404 		break;
405 	    }
406 	    length -= 4 + data_len;
407 	}
408 	break;
409     }
410     case KRB5_FCC_FVNO_3:
411     case KRB5_FCC_FVNO_2:
412     case KRB5_FCC_FVNO_1:
413 	break;
414     default :
415 	krb5_storage_free (sp);
416 	close (fd);
417 	return KRB5_CCACHE_BADVNO;
418     }
419     *ret_sp = sp;
420     *ret_fd = fd;
421     return 0;
422 }
423 
424 static krb5_error_code
425 fcc_get_principal(krb5_context context,
426 		  krb5_ccache id,
427 		  krb5_principal *principal)
428 {
429     krb5_error_code ret;
430     krb5_fcache *f = FCACHE(id);
431     int fd;
432     krb5_storage *sp;
433 
434     ret = init_fcc (context, f, &sp, &fd);
435     if (ret)
436 	return ret;
437     ret = krb5_ret_principal(sp, principal);
438     krb5_storage_free(sp);
439     close(fd);
440     return ret;
441 }
442 
443 static krb5_error_code
444 fcc_get_first (krb5_context context,
445 	       krb5_ccache id,
446 	       krb5_cc_cursor *cursor)
447 {
448     krb5_error_code ret;
449     krb5_principal principal;
450     krb5_fcache *f = FCACHE(id);
451 
452     *cursor = malloc(sizeof(struct fcc_cursor));
453 
454     ret = init_fcc (context, f, &FCC_CURSOR(*cursor)->sp,
455 		    &FCC_CURSOR(*cursor)->fd);
456     if (ret)
457 	return ret;
458     krb5_ret_principal (FCC_CURSOR(*cursor)->sp, &principal);
459     krb5_free_principal (context, principal);
460     return 0;
461 }
462 
463 static krb5_error_code
464 fcc_get_next (krb5_context context,
465 	      krb5_ccache id,
466 	      krb5_cc_cursor *cursor,
467 	      krb5_creds *creds)
468 {
469     return fcc_read_cred (context, FCACHE(id), FCC_CURSOR(*cursor)->sp, creds);
470 }
471 
472 static krb5_error_code
473 fcc_end_get (krb5_context context,
474 	     krb5_ccache id,
475 	     krb5_cc_cursor *cursor)
476 {
477     krb5_storage_free(FCC_CURSOR(*cursor)->sp);
478     close (FCC_CURSOR(*cursor)->fd);
479     free(*cursor);
480     return 0;
481 }
482 
483 static krb5_error_code
484 fcc_remove_cred(krb5_context context,
485 		 krb5_ccache id,
486 		 krb5_flags which,
487 		 krb5_creds *cred)
488 {
489     return 0; /* XXX */
490 }
491 
492 static krb5_error_code
493 fcc_set_flags(krb5_context context,
494 	      krb5_ccache id,
495 	      krb5_flags flags)
496 {
497     return 0; /* XXX */
498 }
499 
500 static krb5_error_code
501 fcc_get_version(krb5_context context,
502 		krb5_ccache id)
503 {
504     return FCACHE(id)->version;
505 }
506 
507 const krb5_cc_ops krb5_fcc_ops = {
508     "FILE",
509     fcc_get_name,
510     fcc_resolve,
511     fcc_gen_new,
512     fcc_initialize,
513     fcc_destroy,
514     fcc_close,
515     fcc_store_cred,
516     NULL, /* fcc_retrieve */
517     fcc_get_principal,
518     fcc_get_first,
519     fcc_get_next,
520     fcc_end_get,
521     fcc_remove_cred,
522     fcc_set_flags,
523     fcc_get_version
524 };
525