fcache.c revision 78527
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
36RCSID("$Id: fcache.c,v 1.33 2001/05/14 06:14:46 assar Exp $");
37
38typedef struct krb5_fcache{
39    char *filename;
40    int version;
41}krb5_fcache;
42
43struct 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
61static char*
62fcc_get_name(krb5_context context,
63	     krb5_ccache id)
64{
65    return FILENAME(id);
66}
67
68static krb5_error_code
69fcc_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
93static int
94scrub_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
123static krb5_error_code
124erase_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
171static krb5_error_code
172fcc_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
204static void
205storage_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
229static krb5_error_code
230fcc_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
283static krb5_error_code
284fcc_close(krb5_context context,
285	  krb5_ccache id)
286{
287    free (FILENAME(id));
288    krb5_data_free(&id->data);
289    return 0;
290}
291
292static krb5_error_code
293fcc_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
304static krb5_error_code
305fcc_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
336static krb5_error_code
337fcc_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
350static krb5_error_code
351init_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
424static krb5_error_code
425fcc_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
443static krb5_error_code
444fcc_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
463static krb5_error_code
464fcc_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
472static krb5_error_code
473fcc_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
483static krb5_error_code
484fcc_remove_cred(krb5_context context,
485		 krb5_ccache id,
486		 krb5_flags which,
487		 krb5_creds *cred)
488{
489    return 0; /* XXX */
490}
491
492static krb5_error_code
493fcc_set_flags(krb5_context context,
494	      krb5_ccache id,
495	      krb5_flags flags)
496{
497    return 0; /* XXX */
498}
499
500static krb5_error_code
501fcc_get_version(krb5_context context,
502		krb5_ccache id)
503{
504    return FCACHE(id)->version;
505}
506
507const 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