xref: /netbsd-src/crypto/external/bsd/heimdal/dist/lib/krb5/fcache.c (revision d3273b5b76f5afaafe308cead5511dbb8df8c5e9)
1*d3273b5bSchristos /*	$NetBSD: fcache.c,v 1.2 2017/01/28 21:31:49 christos Exp $	*/
2ca1c9b0cSelric 
3ca1c9b0cSelric /*
4ca1c9b0cSelric  * Copyright (c) 1997 - 2008 Kungliga Tekniska Högskolan
5ca1c9b0cSelric  * (Royal Institute of Technology, Stockholm, Sweden).
6ca1c9b0cSelric  * All rights reserved.
7ca1c9b0cSelric  *
8ca1c9b0cSelric  * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
9ca1c9b0cSelric  *
10ca1c9b0cSelric  * Redistribution and use in source and binary forms, with or without
11ca1c9b0cSelric  * modification, are permitted provided that the following conditions
12ca1c9b0cSelric  * are met:
13ca1c9b0cSelric  *
14ca1c9b0cSelric  * 1. Redistributions of source code must retain the above copyright
15ca1c9b0cSelric  *    notice, this list of conditions and the following disclaimer.
16ca1c9b0cSelric  *
17ca1c9b0cSelric  * 2. Redistributions in binary form must reproduce the above copyright
18ca1c9b0cSelric  *    notice, this list of conditions and the following disclaimer in the
19ca1c9b0cSelric  *    documentation and/or other materials provided with the distribution.
20ca1c9b0cSelric  *
21ca1c9b0cSelric  * 3. Neither the name of the Institute nor the names of its contributors
22ca1c9b0cSelric  *    may be used to endorse or promote products derived from this software
23ca1c9b0cSelric  *    without specific prior written permission.
24ca1c9b0cSelric  *
25ca1c9b0cSelric  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
26ca1c9b0cSelric  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27ca1c9b0cSelric  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28ca1c9b0cSelric  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
29ca1c9b0cSelric  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30ca1c9b0cSelric  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31ca1c9b0cSelric  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32ca1c9b0cSelric  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33ca1c9b0cSelric  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34ca1c9b0cSelric  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35ca1c9b0cSelric  * SUCH DAMAGE.
36ca1c9b0cSelric  */
37ca1c9b0cSelric 
38ca1c9b0cSelric #include "krb5_locl.h"
39ca1c9b0cSelric 
40ca1c9b0cSelric typedef struct krb5_fcache{
41ca1c9b0cSelric     char *filename;
42ca1c9b0cSelric     int version;
43ca1c9b0cSelric }krb5_fcache;
44ca1c9b0cSelric 
45ca1c9b0cSelric struct fcc_cursor {
46ca1c9b0cSelric     int fd;
47b9d004c6Schristos     off_t cred_start;
48b9d004c6Schristos     off_t cred_end;
49ca1c9b0cSelric     krb5_storage *sp;
50ca1c9b0cSelric };
51ca1c9b0cSelric 
52ca1c9b0cSelric #define KRB5_FCC_FVNO_1 1
53ca1c9b0cSelric #define KRB5_FCC_FVNO_2 2
54ca1c9b0cSelric #define KRB5_FCC_FVNO_3 3
55ca1c9b0cSelric #define KRB5_FCC_FVNO_4 4
56ca1c9b0cSelric 
57ca1c9b0cSelric #define FCC_TAG_DELTATIME 1
58ca1c9b0cSelric 
59ca1c9b0cSelric #define FCACHE(X) ((krb5_fcache*)(X)->data.data)
60ca1c9b0cSelric 
61ca1c9b0cSelric #define FILENAME(X) (FCACHE(X)->filename)
62ca1c9b0cSelric 
63ca1c9b0cSelric #define FCC_CURSOR(C) ((struct fcc_cursor*)(C))
64ca1c9b0cSelric 
65ca1c9b0cSelric static const char* KRB5_CALLCONV
fcc_get_name(krb5_context context,krb5_ccache id)66ca1c9b0cSelric fcc_get_name(krb5_context context,
67ca1c9b0cSelric 	     krb5_ccache id)
68ca1c9b0cSelric {
694f77a458Spettai     if (FCACHE(id) == NULL)
704f77a458Spettai         return NULL;
714f77a458Spettai 
72ca1c9b0cSelric     return FILENAME(id);
73ca1c9b0cSelric }
74ca1c9b0cSelric 
75b9d004c6Schristos KRB5_LIB_FUNCTION int KRB5_LIB_CALL
_krb5_xlock(krb5_context context,int fd,krb5_boolean exclusive,const char * filename)76ca1c9b0cSelric _krb5_xlock(krb5_context context, int fd, krb5_boolean exclusive,
77ca1c9b0cSelric 	    const char *filename)
78ca1c9b0cSelric {
79ca1c9b0cSelric     int ret;
80ca1c9b0cSelric #ifdef HAVE_FCNTL
81ca1c9b0cSelric     struct flock l;
82ca1c9b0cSelric 
83ca1c9b0cSelric     l.l_start = 0;
84ca1c9b0cSelric     l.l_len = 0;
85ca1c9b0cSelric     l.l_type = exclusive ? F_WRLCK : F_RDLCK;
86ca1c9b0cSelric     l.l_whence = SEEK_SET;
87ca1c9b0cSelric     ret = fcntl(fd, F_SETLKW, &l);
88ca1c9b0cSelric #else
89ca1c9b0cSelric     ret = flock(fd, exclusive ? LOCK_EX : LOCK_SH);
90ca1c9b0cSelric #endif
91ca1c9b0cSelric     if(ret < 0)
92ca1c9b0cSelric 	ret = errno;
93ca1c9b0cSelric     if(ret == EACCES) /* fcntl can return EACCES instead of EAGAIN */
94ca1c9b0cSelric 	ret = EAGAIN;
95ca1c9b0cSelric 
96ca1c9b0cSelric     switch (ret) {
97ca1c9b0cSelric     case 0:
98ca1c9b0cSelric 	break;
99ca1c9b0cSelric     case EINVAL: /* filesystem doesn't support locking, let the user have it */
100ca1c9b0cSelric 	ret = 0;
101ca1c9b0cSelric 	break;
102ca1c9b0cSelric     case EAGAIN:
103ca1c9b0cSelric 	krb5_set_error_message(context, ret,
104ca1c9b0cSelric 			       N_("timed out locking cache file %s", "file"),
105ca1c9b0cSelric 			       filename);
106ca1c9b0cSelric 	break;
107ca1c9b0cSelric     default: {
108ca1c9b0cSelric 	char buf[128];
109ca1c9b0cSelric 	rk_strerror_r(ret, buf, sizeof(buf));
110ca1c9b0cSelric 	krb5_set_error_message(context, ret,
111ca1c9b0cSelric 			       N_("error locking cache file %s: %s",
112ca1c9b0cSelric 				  "file, error"), filename, buf);
113ca1c9b0cSelric 	break;
114ca1c9b0cSelric     }
115ca1c9b0cSelric     }
116ca1c9b0cSelric     return ret;
117ca1c9b0cSelric }
118ca1c9b0cSelric 
119b9d004c6Schristos KRB5_LIB_FUNCTION int KRB5_LIB_CALL
_krb5_xunlock(krb5_context context,int fd)120ca1c9b0cSelric _krb5_xunlock(krb5_context context, int fd)
121ca1c9b0cSelric {
122ca1c9b0cSelric     int ret;
123ca1c9b0cSelric #ifdef HAVE_FCNTL
124ca1c9b0cSelric     struct flock l;
125ca1c9b0cSelric     l.l_start = 0;
126ca1c9b0cSelric     l.l_len = 0;
127ca1c9b0cSelric     l.l_type = F_UNLCK;
128ca1c9b0cSelric     l.l_whence = SEEK_SET;
129ca1c9b0cSelric     ret = fcntl(fd, F_SETLKW, &l);
130ca1c9b0cSelric #else
131ca1c9b0cSelric     ret = flock(fd, LOCK_UN);
132ca1c9b0cSelric #endif
133ca1c9b0cSelric     if (ret < 0)
134ca1c9b0cSelric 	ret = errno;
135ca1c9b0cSelric     switch (ret) {
136ca1c9b0cSelric     case 0:
137ca1c9b0cSelric 	break;
138ca1c9b0cSelric     case EINVAL: /* filesystem doesn't support locking, let the user have it */
139ca1c9b0cSelric 	ret = 0;
140ca1c9b0cSelric 	break;
141ca1c9b0cSelric     default: {
142ca1c9b0cSelric 	char buf[128];
143ca1c9b0cSelric 	rk_strerror_r(ret, buf, sizeof(buf));
144ca1c9b0cSelric 	krb5_set_error_message(context, ret,
145ca1c9b0cSelric 			       N_("Failed to unlock file: %s", ""), buf);
146ca1c9b0cSelric 	break;
147ca1c9b0cSelric     }
148ca1c9b0cSelric     }
149ca1c9b0cSelric     return ret;
150ca1c9b0cSelric }
151ca1c9b0cSelric 
152ca1c9b0cSelric static krb5_error_code
write_storage(krb5_context context,krb5_storage * sp,int fd)153ca1c9b0cSelric write_storage(krb5_context context, krb5_storage *sp, int fd)
154ca1c9b0cSelric {
155ca1c9b0cSelric     krb5_error_code ret;
156ca1c9b0cSelric     krb5_data data;
157ca1c9b0cSelric     ssize_t sret;
158ca1c9b0cSelric 
159ca1c9b0cSelric     ret = krb5_storage_to_data(sp, &data);
160ca1c9b0cSelric     if (ret) {
161ca1c9b0cSelric 	krb5_set_error_message(context, ret, N_("malloc: out of memory", ""));
162ca1c9b0cSelric 	return ret;
163ca1c9b0cSelric     }
164ca1c9b0cSelric     sret = write(fd, data.data, data.length);
1654f77a458Spettai     ret = (sret != (ssize_t)data.length);
166ca1c9b0cSelric     krb5_data_free(&data);
167ca1c9b0cSelric     if (ret) {
168ca1c9b0cSelric 	ret = errno;
169ca1c9b0cSelric 	krb5_set_error_message(context, ret,
170ca1c9b0cSelric 			       N_("Failed to write FILE credential data", ""));
171ca1c9b0cSelric 	return ret;
172ca1c9b0cSelric     }
173ca1c9b0cSelric     return 0;
174ca1c9b0cSelric }
175ca1c9b0cSelric 
176ca1c9b0cSelric 
177ca1c9b0cSelric static krb5_error_code KRB5_CALLCONV
fcc_lock(krb5_context context,krb5_ccache id,int fd,krb5_boolean exclusive)178ca1c9b0cSelric fcc_lock(krb5_context context, krb5_ccache id,
179ca1c9b0cSelric 	 int fd, krb5_boolean exclusive)
180ca1c9b0cSelric {
181ca1c9b0cSelric     return _krb5_xlock(context, fd, exclusive, fcc_get_name(context, id));
182ca1c9b0cSelric }
183ca1c9b0cSelric 
184ca1c9b0cSelric static krb5_error_code KRB5_CALLCONV
fcc_unlock(krb5_context context,int fd)185ca1c9b0cSelric fcc_unlock(krb5_context context, int fd)
186ca1c9b0cSelric {
187ca1c9b0cSelric     return _krb5_xunlock(context, fd);
188ca1c9b0cSelric }
189ca1c9b0cSelric 
190ca1c9b0cSelric static krb5_error_code KRB5_CALLCONV
fcc_resolve(krb5_context context,krb5_ccache * id,const char * res)191ca1c9b0cSelric fcc_resolve(krb5_context context, krb5_ccache *id, const char *res)
192ca1c9b0cSelric {
193ca1c9b0cSelric     krb5_fcache *f;
194ca1c9b0cSelric     f = malloc(sizeof(*f));
195ca1c9b0cSelric     if(f == NULL) {
196ca1c9b0cSelric 	krb5_set_error_message(context, KRB5_CC_NOMEM,
197ca1c9b0cSelric 			       N_("malloc: out of memory", ""));
198ca1c9b0cSelric 	return KRB5_CC_NOMEM;
199ca1c9b0cSelric     }
200ca1c9b0cSelric     f->filename = strdup(res);
201ca1c9b0cSelric     if(f->filename == NULL){
202ca1c9b0cSelric 	free(f);
203ca1c9b0cSelric 	krb5_set_error_message(context, KRB5_CC_NOMEM,
204ca1c9b0cSelric 			       N_("malloc: out of memory", ""));
205ca1c9b0cSelric 	return KRB5_CC_NOMEM;
206ca1c9b0cSelric     }
207ca1c9b0cSelric     f->version = 0;
208ca1c9b0cSelric     (*id)->data.data = f;
209ca1c9b0cSelric     (*id)->data.length = sizeof(*f);
210ca1c9b0cSelric     return 0;
211ca1c9b0cSelric }
212ca1c9b0cSelric 
213ca1c9b0cSelric /*
214ca1c9b0cSelric  * Try to scrub the contents of `filename' safely.
215ca1c9b0cSelric  */
216ca1c9b0cSelric 
217ca1c9b0cSelric static int
scrub_file(int fd)218ca1c9b0cSelric scrub_file (int fd)
219ca1c9b0cSelric {
220ca1c9b0cSelric     off_t pos;
221ca1c9b0cSelric     char buf[128];
222ca1c9b0cSelric 
223ca1c9b0cSelric     pos = lseek(fd, 0, SEEK_END);
224ca1c9b0cSelric     if (pos < 0)
225ca1c9b0cSelric         return errno;
226ca1c9b0cSelric     if (lseek(fd, 0, SEEK_SET) < 0)
227ca1c9b0cSelric         return errno;
228ca1c9b0cSelric     memset(buf, 0, sizeof(buf));
229ca1c9b0cSelric     while(pos > 0) {
230b9d004c6Schristos 	ssize_t tmp;
231b9d004c6Schristos 	size_t wr = sizeof(buf);
232b9d004c6Schristos 	if (wr > pos)
233b9d004c6Schristos 	    wr = (size_t)pos;
234b9d004c6Schristos         tmp = write(fd, buf, wr);
235ca1c9b0cSelric 
236ca1c9b0cSelric 	if (tmp < 0)
237ca1c9b0cSelric 	    return errno;
238ca1c9b0cSelric 	pos -= tmp;
239ca1c9b0cSelric     }
240ca1c9b0cSelric #ifdef _MSC_VER
241ca1c9b0cSelric     _commit (fd);
242ca1c9b0cSelric #else
243ca1c9b0cSelric     fsync (fd);
244ca1c9b0cSelric #endif
245ca1c9b0cSelric     return 0;
246ca1c9b0cSelric }
247ca1c9b0cSelric 
248ca1c9b0cSelric /*
249ca1c9b0cSelric  * Erase `filename' if it exists, trying to remove the contents if
250ca1c9b0cSelric  * it's `safe'.  We always try to remove the file, it it exists.  It's
251ca1c9b0cSelric  * only overwritten if it's a regular file (not a symlink and not a
252ca1c9b0cSelric  * hardlink)
253ca1c9b0cSelric  */
254ca1c9b0cSelric 
255b9d004c6Schristos KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
_krb5_erase_file(krb5_context context,const char * filename)256ca1c9b0cSelric _krb5_erase_file(krb5_context context, const char *filename)
257ca1c9b0cSelric {
258ca1c9b0cSelric     int fd;
259ca1c9b0cSelric     struct stat sb1, sb2;
260ca1c9b0cSelric     int ret;
261ca1c9b0cSelric 
262ca1c9b0cSelric     ret = lstat (filename, &sb1);
263ca1c9b0cSelric     if (ret < 0)
264ca1c9b0cSelric 	return errno;
265ca1c9b0cSelric 
266b9d004c6Schristos     fd = open(filename, O_RDWR | O_BINARY | O_CLOEXEC | O_NOFOLLOW);
267ca1c9b0cSelric     if(fd < 0) {
268ca1c9b0cSelric 	if(errno == ENOENT)
269ca1c9b0cSelric 	    return 0;
270ca1c9b0cSelric 	else
271ca1c9b0cSelric 	    return errno;
272ca1c9b0cSelric     }
273ca1c9b0cSelric     rk_cloexec(fd);
274ca1c9b0cSelric     ret = _krb5_xlock(context, fd, 1, filename);
275ca1c9b0cSelric     if (ret) {
276ca1c9b0cSelric 	close(fd);
277ca1c9b0cSelric 	return ret;
278ca1c9b0cSelric     }
279ca1c9b0cSelric     if (unlink(filename) < 0) {
280b9d004c6Schristos 	ret = errno;
281ca1c9b0cSelric 	_krb5_xunlock(context, fd);
282ca1c9b0cSelric         close (fd);
283b9d004c6Schristos 	krb5_set_error_message(context, errno,
284b9d004c6Schristos 	    N_("krb5_cc_destroy: unlinking \"%s\": %s", ""),
285b9d004c6Schristos 	    filename, strerror(ret));
286b9d004c6Schristos         return ret;
287ca1c9b0cSelric     }
288ca1c9b0cSelric     ret = fstat(fd, &sb2);
289ca1c9b0cSelric     if (ret < 0) {
290b9d004c6Schristos 	ret = errno;
291ca1c9b0cSelric 	_krb5_xunlock(context, fd);
292ca1c9b0cSelric 	close (fd);
293b9d004c6Schristos 	return ret;
294ca1c9b0cSelric     }
295ca1c9b0cSelric 
296ca1c9b0cSelric     /* check if someone was playing with symlinks */
297ca1c9b0cSelric 
298ca1c9b0cSelric     if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino) {
299ca1c9b0cSelric 	_krb5_xunlock(context, fd);
300ca1c9b0cSelric 	close(fd);
301ca1c9b0cSelric 	return EPERM;
302ca1c9b0cSelric     }
303ca1c9b0cSelric 
304ca1c9b0cSelric     /* there are still hard links to this file */
305ca1c9b0cSelric 
306ca1c9b0cSelric     if (sb2.st_nlink != 0) {
307ca1c9b0cSelric 	_krb5_xunlock(context, fd);
308ca1c9b0cSelric         close(fd);
309ca1c9b0cSelric         return 0;
310ca1c9b0cSelric     }
311ca1c9b0cSelric 
312ca1c9b0cSelric     ret = scrub_file(fd);
313ca1c9b0cSelric     if (ret) {
314ca1c9b0cSelric 	_krb5_xunlock(context, fd);
315ca1c9b0cSelric 	close(fd);
316ca1c9b0cSelric 	return ret;
317ca1c9b0cSelric     }
318ca1c9b0cSelric     ret = _krb5_xunlock(context, fd);
319ca1c9b0cSelric     close(fd);
320ca1c9b0cSelric     return ret;
321ca1c9b0cSelric }
322ca1c9b0cSelric 
323ca1c9b0cSelric static krb5_error_code KRB5_CALLCONV
fcc_gen_new(krb5_context context,krb5_ccache * id)324ca1c9b0cSelric fcc_gen_new(krb5_context context, krb5_ccache *id)
325ca1c9b0cSelric {
326ca1c9b0cSelric     char *file = NULL, *exp_file = NULL;
327ca1c9b0cSelric     krb5_error_code ret;
328ca1c9b0cSelric     krb5_fcache *f;
329ca1c9b0cSelric     int fd;
330ca1c9b0cSelric 
331ca1c9b0cSelric     f = malloc(sizeof(*f));
332ca1c9b0cSelric     if(f == NULL) {
333ca1c9b0cSelric 	krb5_set_error_message(context, KRB5_CC_NOMEM,
334ca1c9b0cSelric 			       N_("malloc: out of memory", ""));
335ca1c9b0cSelric 	return KRB5_CC_NOMEM;
336ca1c9b0cSelric     }
337ca1c9b0cSelric     ret = asprintf(&file, "%sXXXXXX", KRB5_DEFAULT_CCFILE_ROOT);
338ca1c9b0cSelric     if(ret < 0 || file == NULL) {
339ca1c9b0cSelric 	free(f);
340ca1c9b0cSelric 	krb5_set_error_message(context, KRB5_CC_NOMEM,
341ca1c9b0cSelric 			       N_("malloc: out of memory", ""));
342ca1c9b0cSelric 	return KRB5_CC_NOMEM;
343ca1c9b0cSelric     }
344b9d004c6Schristos     ret = _krb5_expand_path_tokens(context, file, 1, &exp_file);
345ca1c9b0cSelric     free(file);
346b9d004c6Schristos     if (ret) {
347b9d004c6Schristos 	free(f);
348ca1c9b0cSelric 	return ret;
349b9d004c6Schristos     }
350ca1c9b0cSelric 
351ca1c9b0cSelric     file = exp_file;
352ca1c9b0cSelric 
353ca1c9b0cSelric     fd = mkstemp(exp_file);
354ca1c9b0cSelric     if(fd < 0) {
355b9d004c6Schristos 	ret = (krb5_error_code)errno;
356b9d004c6Schristos 	krb5_set_error_message(context, ret, N_("mkstemp %s failed", ""), exp_file);
357ca1c9b0cSelric 	free(f);
358ca1c9b0cSelric 	free(exp_file);
359b9d004c6Schristos 	return ret;
360ca1c9b0cSelric     }
361ca1c9b0cSelric     close(fd);
362ca1c9b0cSelric     f->filename = exp_file;
363ca1c9b0cSelric     f->version = 0;
364ca1c9b0cSelric     (*id)->data.data = f;
365ca1c9b0cSelric     (*id)->data.length = sizeof(*f);
366ca1c9b0cSelric     return 0;
367ca1c9b0cSelric }
368ca1c9b0cSelric 
369ca1c9b0cSelric static void
storage_set_flags(krb5_context context,krb5_storage * sp,int vno)370ca1c9b0cSelric storage_set_flags(krb5_context context, krb5_storage *sp, int vno)
371ca1c9b0cSelric {
372ca1c9b0cSelric     int flags = 0;
373ca1c9b0cSelric     switch(vno) {
374ca1c9b0cSelric     case KRB5_FCC_FVNO_1:
375ca1c9b0cSelric 	flags |= KRB5_STORAGE_PRINCIPAL_WRONG_NUM_COMPONENTS;
376ca1c9b0cSelric 	flags |= KRB5_STORAGE_PRINCIPAL_NO_NAME_TYPE;
377ca1c9b0cSelric 	flags |= KRB5_STORAGE_HOST_BYTEORDER;
378ca1c9b0cSelric 	break;
379ca1c9b0cSelric     case KRB5_FCC_FVNO_2:
380ca1c9b0cSelric 	flags |= KRB5_STORAGE_HOST_BYTEORDER;
381ca1c9b0cSelric 	break;
382ca1c9b0cSelric     case KRB5_FCC_FVNO_3:
383ca1c9b0cSelric 	flags |= KRB5_STORAGE_KEYBLOCK_KEYTYPE_TWICE;
384ca1c9b0cSelric 	break;
385ca1c9b0cSelric     case KRB5_FCC_FVNO_4:
386ca1c9b0cSelric 	break;
387ca1c9b0cSelric     default:
388ca1c9b0cSelric 	krb5_abortx(context,
389ca1c9b0cSelric 		    "storage_set_flags called with bad vno (%x)", vno);
390ca1c9b0cSelric     }
391ca1c9b0cSelric     krb5_storage_set_flags(sp, flags);
392ca1c9b0cSelric }
393ca1c9b0cSelric 
394ca1c9b0cSelric static krb5_error_code KRB5_CALLCONV
fcc_open(krb5_context context,krb5_ccache id,const char * operation,int * fd_ret,int flags,mode_t mode)395ca1c9b0cSelric fcc_open(krb5_context context,
396ca1c9b0cSelric 	 krb5_ccache id,
397b9d004c6Schristos 	 const char *operation,
398ca1c9b0cSelric 	 int *fd_ret,
399ca1c9b0cSelric 	 int flags,
400ca1c9b0cSelric 	 mode_t mode)
401ca1c9b0cSelric {
402ca1c9b0cSelric     krb5_boolean exclusive = ((flags | O_WRONLY) == flags ||
403ca1c9b0cSelric 			      (flags | O_RDWR) == flags);
404ca1c9b0cSelric     krb5_error_code ret;
4054f77a458Spettai     const char *filename;
406b9d004c6Schristos     struct stat sb1, sb2;
407b9d004c6Schristos #ifndef _WIN32
408b9d004c6Schristos     struct stat sb3;
409b9d004c6Schristos     size_t tries = 3;
410b9d004c6Schristos #endif
411b9d004c6Schristos     int strict_checking;
412ca1c9b0cSelric     int fd;
4134f77a458Spettai 
414b9d004c6Schristos     flags |= O_BINARY | O_CLOEXEC | O_NOFOLLOW;
415b9d004c6Schristos 
416b9d004c6Schristos     *fd_ret = -1;
417b9d004c6Schristos 
4184f77a458Spettai     if (FCACHE(id) == NULL)
4194f77a458Spettai         return krb5_einval(context, 2);
4204f77a458Spettai 
4214f77a458Spettai     filename = FILENAME(id);
4224f77a458Spettai 
423b9d004c6Schristos     strict_checking = (flags & O_CREAT) == 0 &&
424b9d004c6Schristos 	(context->flags & KRB5_CTX_F_FCACHE_STRICT_CHECKING) != 0;
425b9d004c6Schristos 
426b9d004c6Schristos again:
427b9d004c6Schristos     memset(&sb1, 0, sizeof(sb1));
428b9d004c6Schristos     ret = lstat(filename, &sb1);
429b9d004c6Schristos     if (ret == 0) {
430b9d004c6Schristos 	if (!S_ISREG(sb1.st_mode)) {
431b9d004c6Schristos 	    krb5_set_error_message(context, EPERM,
432b9d004c6Schristos 				   N_("Refuses to open symlinks for caches FILE:%s", ""), filename);
433b9d004c6Schristos 	    return EPERM;
434b9d004c6Schristos 	}
435b9d004c6Schristos     } else if (errno != ENOENT || !(flags & O_CREAT)) {
436b9d004c6Schristos 	krb5_set_error_message(context, errno, N_("%s lstat(%s)", "file, error"),
437b9d004c6Schristos 			       operation, filename);
438b9d004c6Schristos 	return errno;
439b9d004c6Schristos     }
440b9d004c6Schristos 
441ca1c9b0cSelric     fd = open(filename, flags, mode);
442ca1c9b0cSelric     if(fd < 0) {
443ca1c9b0cSelric 	char buf[128];
444ca1c9b0cSelric 	ret = errno;
445ca1c9b0cSelric 	rk_strerror_r(ret, buf, sizeof(buf));
446b9d004c6Schristos 	krb5_set_error_message(context, ret, N_("%s open(%s): %s", "file, error"),
447b9d004c6Schristos 			       operation, filename, buf);
448ca1c9b0cSelric 	return ret;
449ca1c9b0cSelric     }
450ca1c9b0cSelric     rk_cloexec(fd);
451ca1c9b0cSelric 
452b9d004c6Schristos     ret = fstat(fd, &sb2);
453b9d004c6Schristos     if (ret < 0) {
454b9d004c6Schristos 	krb5_clear_error_message(context);
455b9d004c6Schristos 	close(fd);
456b9d004c6Schristos 	return errno;
457b9d004c6Schristos     }
458b9d004c6Schristos 
459b9d004c6Schristos     if (!S_ISREG(sb2.st_mode)) {
460b9d004c6Schristos 	krb5_set_error_message(context, EPERM, N_("Refuses to open non files caches: FILE:%s", ""), filename);
461b9d004c6Schristos 	close(fd);
462b9d004c6Schristos 	return EPERM;
463b9d004c6Schristos     }
464b9d004c6Schristos 
465b9d004c6Schristos #ifndef _WIN32
466b9d004c6Schristos     if (sb1.st_dev && sb1.st_ino &&
467b9d004c6Schristos 	(sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino)) {
468b9d004c6Schristos 	/*
469b9d004c6Schristos 	 * Perhaps we raced with a rename().  To complain about
470b9d004c6Schristos 	 * symlinks in that case would cause unnecessary concern, so
471b9d004c6Schristos 	 * we check for that possibility and loop.  This has no
472b9d004c6Schristos 	 * TOCTOU problems because we redo the open().  We could also
473b9d004c6Schristos 	 * not do any of this checking if O_NOFOLLOW != 0...
474b9d004c6Schristos 	 */
475b9d004c6Schristos 	close(fd);
476b9d004c6Schristos 	ret = lstat(filename, &sb3);
477b9d004c6Schristos 	if (ret || sb1.st_dev != sb2.st_dev ||
478b9d004c6Schristos 	    sb3.st_dev != sb2.st_dev || sb3.st_ino != sb2.st_ino) {
479b9d004c6Schristos 	    krb5_set_error_message(context, EPERM, N_("Refuses to open possible symlink for caches: FILE:%s", ""), filename);
480b9d004c6Schristos 	    return EPERM;
481b9d004c6Schristos 	}
482b9d004c6Schristos 	if (--tries == 0) {
483b9d004c6Schristos 	    krb5_set_error_message(context, EPERM, N_("Raced too many times with renames of FILE:%s", ""), filename);
484b9d004c6Schristos 	    return EPERM;
485b9d004c6Schristos 	}
486b9d004c6Schristos 	goto again;
487b9d004c6Schristos     }
488b9d004c6Schristos #endif
489b9d004c6Schristos 
490b9d004c6Schristos     /*
491b9d004c6Schristos      * /tmp (or wherever default ccaches go) might not be on its own
492b9d004c6Schristos      * filesystem, or on a filesystem different /etc, say, and even if
493b9d004c6Schristos      * it were, suppose a user hard-links another's ccache to her
494b9d004c6Schristos      * default ccache, then runs a set-uid program that will user her
495b9d004c6Schristos      * default ccache (even if it ignores KRB5CCNAME)...
496b9d004c6Schristos      *
497b9d004c6Schristos      * Default ccache locations should really be on per-user non-tmp
498b9d004c6Schristos      * locations on tmpfs "run" directories.  But we don't know here
499b9d004c6Schristos      * that this is the case.  Thus: no hard-links, no symlinks.
500b9d004c6Schristos      */
501b9d004c6Schristos     if (sb2.st_nlink != 1) {
502b9d004c6Schristos 	krb5_set_error_message(context, EPERM, N_("Refuses to open hardlinks for caches FILE:%s", ""), filename);
503b9d004c6Schristos 	close(fd);
504b9d004c6Schristos 	return EPERM;
505b9d004c6Schristos     }
506b9d004c6Schristos 
507b9d004c6Schristos     if (strict_checking) {
508b9d004c6Schristos #ifndef _WIN32
509b9d004c6Schristos 	/*
510b9d004c6Schristos 	 * XXX WIN32: Needs to have ACL checking code!
511b9d004c6Schristos 	 * st_mode comes out as 100666, and st_uid is no use.
512b9d004c6Schristos 	 */
513b9d004c6Schristos 	/*
514b9d004c6Schristos 	 * XXX Should probably add options to improve control over this
515b9d004c6Schristos 	 * check.  We might want strict checking of everything except
516b9d004c6Schristos 	 * this.
517b9d004c6Schristos 	 */
518b9d004c6Schristos 	if (sb2.st_uid != geteuid()) {
519b9d004c6Schristos 	    krb5_set_error_message(context, EPERM, N_("Refuses to open cache files not own by myself FILE:%s (owned by %d)", ""), filename, (int)sb2.st_uid);
520b9d004c6Schristos 	    close(fd);
521b9d004c6Schristos 	    return EPERM;
522b9d004c6Schristos 	}
523b9d004c6Schristos 	if ((sb2.st_mode & 077) != 0) {
524b9d004c6Schristos 	    krb5_set_error_message(context, EPERM,
525b9d004c6Schristos 				   N_("Refuses to open group/other readable files FILE:%s", ""), filename);
526b9d004c6Schristos 	    close(fd);
527b9d004c6Schristos 	    return EPERM;
528b9d004c6Schristos 	}
529b9d004c6Schristos #endif
530b9d004c6Schristos     }
531b9d004c6Schristos 
532ca1c9b0cSelric     if((ret = fcc_lock(context, id, fd, exclusive)) != 0) {
533ca1c9b0cSelric 	close(fd);
534ca1c9b0cSelric 	return ret;
535ca1c9b0cSelric     }
536ca1c9b0cSelric     *fd_ret = fd;
537ca1c9b0cSelric     return 0;
538ca1c9b0cSelric }
539ca1c9b0cSelric 
540ca1c9b0cSelric static krb5_error_code KRB5_CALLCONV
fcc_initialize(krb5_context context,krb5_ccache id,krb5_principal primary_principal)541ca1c9b0cSelric fcc_initialize(krb5_context context,
542ca1c9b0cSelric 	       krb5_ccache id,
543ca1c9b0cSelric 	       krb5_principal primary_principal)
544ca1c9b0cSelric {
545ca1c9b0cSelric     krb5_fcache *f = FCACHE(id);
546ca1c9b0cSelric     int ret = 0;
547ca1c9b0cSelric     int fd;
548ca1c9b0cSelric 
5494f77a458Spettai     if (f == NULL)
5504f77a458Spettai         return krb5_einval(context, 2);
5514f77a458Spettai 
5524f77a458Spettai     unlink (f->filename);
553ca1c9b0cSelric 
554b9d004c6Schristos     ret = fcc_open(context, id, "initialize", &fd, O_RDWR | O_CREAT | O_EXCL, 0600);
555ca1c9b0cSelric     if(ret)
556ca1c9b0cSelric 	return ret;
557ca1c9b0cSelric     {
558ca1c9b0cSelric 	krb5_storage *sp;
559ca1c9b0cSelric 	sp = krb5_storage_emem();
560ca1c9b0cSelric 	krb5_storage_set_eof_code(sp, KRB5_CC_END);
561ca1c9b0cSelric 	if(context->fcache_vno != 0)
562ca1c9b0cSelric 	    f->version = context->fcache_vno;
563ca1c9b0cSelric 	else
564ca1c9b0cSelric 	    f->version = KRB5_FCC_FVNO_4;
565ca1c9b0cSelric 	ret |= krb5_store_int8(sp, 5);
566ca1c9b0cSelric 	ret |= krb5_store_int8(sp, f->version);
567ca1c9b0cSelric 	storage_set_flags(context, sp, f->version);
568ca1c9b0cSelric 	if(f->version == KRB5_FCC_FVNO_4 && ret == 0) {
569ca1c9b0cSelric 	    /* V4 stuff */
570ca1c9b0cSelric 	    if (context->kdc_sec_offset) {
571ca1c9b0cSelric 		ret |= krb5_store_int16 (sp, 12); /* length */
572ca1c9b0cSelric 		ret |= krb5_store_int16 (sp, FCC_TAG_DELTATIME); /* Tag */
573ca1c9b0cSelric 		ret |= krb5_store_int16 (sp, 8); /* length of data */
574ca1c9b0cSelric 		ret |= krb5_store_int32 (sp, context->kdc_sec_offset);
575ca1c9b0cSelric 		ret |= krb5_store_int32 (sp, context->kdc_usec_offset);
576ca1c9b0cSelric 	    } else {
577ca1c9b0cSelric 		ret |= krb5_store_int16 (sp, 0);
578ca1c9b0cSelric 	    }
579ca1c9b0cSelric 	}
580ca1c9b0cSelric 	ret |= krb5_store_principal(sp, primary_principal);
581ca1c9b0cSelric 
582ca1c9b0cSelric 	ret |= write_storage(context, sp, fd);
583ca1c9b0cSelric 
584ca1c9b0cSelric 	krb5_storage_free(sp);
585ca1c9b0cSelric     }
586ca1c9b0cSelric     fcc_unlock(context, fd);
587ca1c9b0cSelric     if (close(fd) < 0)
588ca1c9b0cSelric 	if (ret == 0) {
589ca1c9b0cSelric 	    char buf[128];
590ca1c9b0cSelric 	    ret = errno;
591ca1c9b0cSelric 	    rk_strerror_r(ret, buf, sizeof(buf));
592ca1c9b0cSelric 	    krb5_set_error_message(context, ret, N_("close %s: %s", ""),
593ca1c9b0cSelric 				   FILENAME(id), buf);
594ca1c9b0cSelric 	}
595ca1c9b0cSelric     return ret;
596ca1c9b0cSelric }
597ca1c9b0cSelric 
598ca1c9b0cSelric static krb5_error_code KRB5_CALLCONV
fcc_close(krb5_context context,krb5_ccache id)599ca1c9b0cSelric fcc_close(krb5_context context,
600ca1c9b0cSelric 	  krb5_ccache id)
601ca1c9b0cSelric {
6024f77a458Spettai     if (FCACHE(id) == NULL)
6034f77a458Spettai         return krb5_einval(context, 2);
6044f77a458Spettai 
605ca1c9b0cSelric     free (FILENAME(id));
606ca1c9b0cSelric     krb5_data_free(&id->data);
607ca1c9b0cSelric     return 0;
608ca1c9b0cSelric }
609ca1c9b0cSelric 
610ca1c9b0cSelric static krb5_error_code KRB5_CALLCONV
fcc_destroy(krb5_context context,krb5_ccache id)611ca1c9b0cSelric fcc_destroy(krb5_context context,
612ca1c9b0cSelric 	    krb5_ccache id)
613ca1c9b0cSelric {
6144f77a458Spettai     if (FCACHE(id) == NULL)
6154f77a458Spettai         return krb5_einval(context, 2);
6164f77a458Spettai 
617b9d004c6Schristos     return _krb5_erase_file(context, FILENAME(id));
618ca1c9b0cSelric }
619ca1c9b0cSelric 
620ca1c9b0cSelric static krb5_error_code KRB5_CALLCONV
fcc_store_cred(krb5_context context,krb5_ccache id,krb5_creds * creds)621ca1c9b0cSelric fcc_store_cred(krb5_context context,
622ca1c9b0cSelric 	       krb5_ccache id,
623ca1c9b0cSelric 	       krb5_creds *creds)
624ca1c9b0cSelric {
625ca1c9b0cSelric     int ret;
626ca1c9b0cSelric     int fd;
627ca1c9b0cSelric 
628b9d004c6Schristos     ret = fcc_open(context, id, "store", &fd, O_WRONLY | O_APPEND, 0);
629ca1c9b0cSelric     if(ret)
630ca1c9b0cSelric 	return ret;
631ca1c9b0cSelric     {
632ca1c9b0cSelric 	krb5_storage *sp;
633ca1c9b0cSelric 
634ca1c9b0cSelric 	sp = krb5_storage_emem();
635ca1c9b0cSelric 	krb5_storage_set_eof_code(sp, KRB5_CC_END);
636ca1c9b0cSelric 	storage_set_flags(context, sp, FCACHE(id)->version);
637ca1c9b0cSelric 	ret = krb5_store_creds(sp, creds);
638ca1c9b0cSelric 	if (ret == 0)
639ca1c9b0cSelric 	    ret = write_storage(context, sp, fd);
640ca1c9b0cSelric 	krb5_storage_free(sp);
641ca1c9b0cSelric     }
642ca1c9b0cSelric     fcc_unlock(context, fd);
643ca1c9b0cSelric     if (close(fd) < 0) {
644ca1c9b0cSelric 	if (ret == 0) {
645ca1c9b0cSelric 	    char buf[128];
646ca1c9b0cSelric 	    ret = errno;
647b9d004c6Schristos 	    rk_strerror_r(ret, buf, sizeof(buf));
648ca1c9b0cSelric 	    krb5_set_error_message(context, ret, N_("close %s: %s", ""),
649ca1c9b0cSelric 				   FILENAME(id), buf);
650ca1c9b0cSelric 	}
651ca1c9b0cSelric     }
652ca1c9b0cSelric     return ret;
653ca1c9b0cSelric }
654ca1c9b0cSelric 
655ca1c9b0cSelric static krb5_error_code
init_fcc(krb5_context context,krb5_ccache id,const char * operation,krb5_storage ** ret_sp,int * ret_fd,krb5_deltat * kdc_offset)656ca1c9b0cSelric init_fcc(krb5_context context,
657ca1c9b0cSelric 	 krb5_ccache id,
658b9d004c6Schristos 	 const char *operation,
659ca1c9b0cSelric 	 krb5_storage **ret_sp,
660ca1c9b0cSelric 	 int *ret_fd,
661ca1c9b0cSelric 	 krb5_deltat *kdc_offset)
662ca1c9b0cSelric {
663ca1c9b0cSelric     int fd;
664ca1c9b0cSelric     int8_t pvno, tag;
665ca1c9b0cSelric     krb5_storage *sp;
666ca1c9b0cSelric     krb5_error_code ret;
667ca1c9b0cSelric 
668b9d004c6Schristos     *ret_fd = -1;
669b9d004c6Schristos     *ret_sp = NULL;
670ca1c9b0cSelric     if (kdc_offset)
671ca1c9b0cSelric 	*kdc_offset = 0;
672ca1c9b0cSelric 
673b9d004c6Schristos     ret = fcc_open(context, id, operation, &fd, O_RDONLY, 0);
674ca1c9b0cSelric     if(ret)
675ca1c9b0cSelric 	return ret;
676ca1c9b0cSelric 
677ca1c9b0cSelric     sp = krb5_storage_from_fd(fd);
678ca1c9b0cSelric     if(sp == NULL) {
679ca1c9b0cSelric 	krb5_clear_error_message(context);
680ca1c9b0cSelric 	ret = ENOMEM;
681ca1c9b0cSelric 	goto out;
682ca1c9b0cSelric     }
683ca1c9b0cSelric     krb5_storage_set_eof_code(sp, KRB5_CC_END);
684ca1c9b0cSelric     ret = krb5_ret_int8(sp, &pvno);
685ca1c9b0cSelric     if (ret != 0) {
686ca1c9b0cSelric 	if(ret == KRB5_CC_END) {
687ca1c9b0cSelric 	    ret = ENOENT;
688ca1c9b0cSelric 	    krb5_set_error_message(context, ret,
689ca1c9b0cSelric 				   N_("Empty credential cache file: %s", ""),
690ca1c9b0cSelric 				   FILENAME(id));
691ca1c9b0cSelric 	} else
692ca1c9b0cSelric 	    krb5_set_error_message(context, ret, N_("Error reading pvno "
693ca1c9b0cSelric 						    "in cache file: %s", ""),
694ca1c9b0cSelric 				   FILENAME(id));
695ca1c9b0cSelric 	goto out;
696ca1c9b0cSelric     }
697ca1c9b0cSelric     if (pvno != 5) {
698ca1c9b0cSelric 	ret = KRB5_CCACHE_BADVNO;
699ca1c9b0cSelric 	krb5_set_error_message(context, ret, N_("Bad version number in credential "
700ca1c9b0cSelric 						"cache file: %s", ""),
701ca1c9b0cSelric 			       FILENAME(id));
702ca1c9b0cSelric 	goto out;
703ca1c9b0cSelric     }
704ca1c9b0cSelric     ret = krb5_ret_int8(sp, &tag); /* should not be host byte order */
705ca1c9b0cSelric     if (ret != 0) {
706ca1c9b0cSelric 	ret = KRB5_CC_FORMAT;
707ca1c9b0cSelric 	krb5_set_error_message(context, ret, "Error reading tag in "
708ca1c9b0cSelric 			      "cache file: %s", FILENAME(id));
709ca1c9b0cSelric 	goto out;
710ca1c9b0cSelric     }
711ca1c9b0cSelric     FCACHE(id)->version = tag;
712ca1c9b0cSelric     storage_set_flags(context, sp, FCACHE(id)->version);
713ca1c9b0cSelric     switch (tag) {
714ca1c9b0cSelric     case KRB5_FCC_FVNO_4: {
715ca1c9b0cSelric 	int16_t length;
716ca1c9b0cSelric 
717ca1c9b0cSelric 	ret = krb5_ret_int16 (sp, &length);
718ca1c9b0cSelric 	if(ret) {
719ca1c9b0cSelric 	    ret = KRB5_CC_FORMAT;
720ca1c9b0cSelric 	    krb5_set_error_message(context, ret,
721ca1c9b0cSelric 				   N_("Error reading tag length in "
722ca1c9b0cSelric 				      "cache file: %s", ""), FILENAME(id));
723ca1c9b0cSelric 	    goto out;
724ca1c9b0cSelric 	}
725ca1c9b0cSelric 	while(length > 0) {
726ca1c9b0cSelric 	    int16_t dtag, data_len;
727ca1c9b0cSelric 	    int i;
728ca1c9b0cSelric 	    int8_t dummy;
729ca1c9b0cSelric 
730ca1c9b0cSelric 	    ret = krb5_ret_int16 (sp, &dtag);
731ca1c9b0cSelric 	    if(ret) {
732ca1c9b0cSelric 		ret = KRB5_CC_FORMAT;
733ca1c9b0cSelric 		krb5_set_error_message(context, ret, N_("Error reading dtag in "
734ca1c9b0cSelric 							"cache file: %s", ""),
735ca1c9b0cSelric 				       FILENAME(id));
736ca1c9b0cSelric 		goto out;
737ca1c9b0cSelric 	    }
738ca1c9b0cSelric 	    ret = krb5_ret_int16 (sp, &data_len);
739ca1c9b0cSelric 	    if(ret) {
740ca1c9b0cSelric 		ret = KRB5_CC_FORMAT;
741ca1c9b0cSelric 		krb5_set_error_message(context, ret,
742ca1c9b0cSelric 				       N_("Error reading dlength "
743ca1c9b0cSelric 					  "in cache file: %s",""),
744ca1c9b0cSelric 				       FILENAME(id));
745ca1c9b0cSelric 		goto out;
746ca1c9b0cSelric 	    }
747ca1c9b0cSelric 	    switch (dtag) {
748ca1c9b0cSelric 	    case FCC_TAG_DELTATIME : {
749ca1c9b0cSelric 		int32_t offset;
750ca1c9b0cSelric 
751ca1c9b0cSelric 		ret = krb5_ret_int32 (sp, &offset);
752ca1c9b0cSelric 		ret |= krb5_ret_int32 (sp, &context->kdc_usec_offset);
753ca1c9b0cSelric 		if(ret) {
754ca1c9b0cSelric 		    ret = KRB5_CC_FORMAT;
755ca1c9b0cSelric 		    krb5_set_error_message(context, ret,
756ca1c9b0cSelric 					   N_("Error reading kdc_sec in "
757ca1c9b0cSelric 					      "cache file: %s", ""),
758ca1c9b0cSelric 					   FILENAME(id));
759ca1c9b0cSelric 		    goto out;
760ca1c9b0cSelric 		}
761ca1c9b0cSelric 		context->kdc_sec_offset = offset;
762ca1c9b0cSelric 		if (kdc_offset)
763ca1c9b0cSelric 		    *kdc_offset = offset;
764ca1c9b0cSelric 		break;
765ca1c9b0cSelric 	    }
766ca1c9b0cSelric 	    default :
767ca1c9b0cSelric 		for (i = 0; i < data_len; ++i) {
768ca1c9b0cSelric 		    ret = krb5_ret_int8 (sp, &dummy);
769ca1c9b0cSelric 		    if(ret) {
770ca1c9b0cSelric 			ret = KRB5_CC_FORMAT;
771ca1c9b0cSelric 			krb5_set_error_message(context, ret,
772ca1c9b0cSelric 					       N_("Error reading unknown "
773ca1c9b0cSelric 						  "tag in cache file: %s", ""),
774ca1c9b0cSelric 					       FILENAME(id));
775ca1c9b0cSelric 			goto out;
776ca1c9b0cSelric 		    }
777ca1c9b0cSelric 		}
778ca1c9b0cSelric 		break;
779ca1c9b0cSelric 	    }
780ca1c9b0cSelric 	    length -= 4 + data_len;
781ca1c9b0cSelric 	}
782ca1c9b0cSelric 	break;
783ca1c9b0cSelric     }
784ca1c9b0cSelric     case KRB5_FCC_FVNO_3:
785ca1c9b0cSelric     case KRB5_FCC_FVNO_2:
786ca1c9b0cSelric     case KRB5_FCC_FVNO_1:
787ca1c9b0cSelric 	break;
788ca1c9b0cSelric     default :
789ca1c9b0cSelric 	ret = KRB5_CCACHE_BADVNO;
790ca1c9b0cSelric 	krb5_set_error_message(context, ret,
791ca1c9b0cSelric 			       N_("Unknown version number (%d) in "
792ca1c9b0cSelric 				  "credential cache file: %s", ""),
793ca1c9b0cSelric 			       (int)tag, FILENAME(id));
794ca1c9b0cSelric 	goto out;
795ca1c9b0cSelric     }
796ca1c9b0cSelric     *ret_sp = sp;
797ca1c9b0cSelric     *ret_fd = fd;
798ca1c9b0cSelric 
799ca1c9b0cSelric     return 0;
800ca1c9b0cSelric   out:
801ca1c9b0cSelric     if(sp != NULL)
802ca1c9b0cSelric 	krb5_storage_free(sp);
803ca1c9b0cSelric     fcc_unlock(context, fd);
804ca1c9b0cSelric     close(fd);
805ca1c9b0cSelric     return ret;
806ca1c9b0cSelric }
807ca1c9b0cSelric 
808ca1c9b0cSelric static krb5_error_code KRB5_CALLCONV
fcc_get_principal(krb5_context context,krb5_ccache id,krb5_principal * principal)809ca1c9b0cSelric fcc_get_principal(krb5_context context,
810ca1c9b0cSelric 		  krb5_ccache id,
811ca1c9b0cSelric 		  krb5_principal *principal)
812ca1c9b0cSelric {
813ca1c9b0cSelric     krb5_error_code ret;
814ca1c9b0cSelric     int fd;
815ca1c9b0cSelric     krb5_storage *sp;
816ca1c9b0cSelric 
817b9d004c6Schristos     ret = init_fcc (context, id, "get-principal", &sp, &fd, NULL);
818ca1c9b0cSelric     if (ret)
819ca1c9b0cSelric 	return ret;
820ca1c9b0cSelric     ret = krb5_ret_principal(sp, principal);
821ca1c9b0cSelric     if (ret)
822ca1c9b0cSelric 	krb5_clear_error_message(context);
823ca1c9b0cSelric     krb5_storage_free(sp);
824ca1c9b0cSelric     fcc_unlock(context, fd);
825ca1c9b0cSelric     close(fd);
826ca1c9b0cSelric     return ret;
827ca1c9b0cSelric }
828ca1c9b0cSelric 
829ca1c9b0cSelric static krb5_error_code KRB5_CALLCONV
830ca1c9b0cSelric fcc_end_get (krb5_context context,
831ca1c9b0cSelric 	     krb5_ccache id,
832ca1c9b0cSelric 	     krb5_cc_cursor *cursor);
833ca1c9b0cSelric 
834ca1c9b0cSelric static krb5_error_code KRB5_CALLCONV
fcc_get_first(krb5_context context,krb5_ccache id,krb5_cc_cursor * cursor)835ca1c9b0cSelric fcc_get_first (krb5_context context,
836ca1c9b0cSelric 	       krb5_ccache id,
837ca1c9b0cSelric 	       krb5_cc_cursor *cursor)
838ca1c9b0cSelric {
839ca1c9b0cSelric     krb5_error_code ret;
840ca1c9b0cSelric     krb5_principal principal;
841ca1c9b0cSelric 
8424f77a458Spettai     if (FCACHE(id) == NULL)
8434f77a458Spettai         return krb5_einval(context, 2);
8444f77a458Spettai 
845ca1c9b0cSelric     *cursor = malloc(sizeof(struct fcc_cursor));
846ca1c9b0cSelric     if (*cursor == NULL) {
847ca1c9b0cSelric         krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));
848ca1c9b0cSelric 	return ENOMEM;
849ca1c9b0cSelric     }
850ca1c9b0cSelric     memset(*cursor, 0, sizeof(struct fcc_cursor));
851ca1c9b0cSelric 
852b9d004c6Schristos     ret = init_fcc(context, id, "get-frist", &FCC_CURSOR(*cursor)->sp,
853ca1c9b0cSelric 		   &FCC_CURSOR(*cursor)->fd, NULL);
854ca1c9b0cSelric     if (ret) {
855ca1c9b0cSelric 	free(*cursor);
856ca1c9b0cSelric 	*cursor = NULL;
857ca1c9b0cSelric 	return ret;
858ca1c9b0cSelric     }
859ca1c9b0cSelric     ret = krb5_ret_principal (FCC_CURSOR(*cursor)->sp, &principal);
860ca1c9b0cSelric     if(ret) {
861ca1c9b0cSelric 	krb5_clear_error_message(context);
862ca1c9b0cSelric 	fcc_end_get(context, id, cursor);
863ca1c9b0cSelric 	return ret;
864ca1c9b0cSelric     }
865ca1c9b0cSelric     krb5_free_principal (context, principal);
866ca1c9b0cSelric     fcc_unlock(context, FCC_CURSOR(*cursor)->fd);
867ca1c9b0cSelric     return 0;
868ca1c9b0cSelric }
869ca1c9b0cSelric 
870ca1c9b0cSelric static krb5_error_code KRB5_CALLCONV
fcc_get_next(krb5_context context,krb5_ccache id,krb5_cc_cursor * cursor,krb5_creds * creds)871ca1c9b0cSelric fcc_get_next (krb5_context context,
872ca1c9b0cSelric 	      krb5_ccache id,
873ca1c9b0cSelric 	      krb5_cc_cursor *cursor,
874ca1c9b0cSelric 	      krb5_creds *creds)
875ca1c9b0cSelric {
876ca1c9b0cSelric     krb5_error_code ret;
8774f77a458Spettai 
8784f77a458Spettai     if (FCACHE(id) == NULL)
8794f77a458Spettai         return krb5_einval(context, 2);
8804f77a458Spettai 
8814f77a458Spettai     if (FCC_CURSOR(*cursor) == NULL)
8824f77a458Spettai         return krb5_einval(context, 3);
8834f77a458Spettai 
884ca1c9b0cSelric     if((ret = fcc_lock(context, id, FCC_CURSOR(*cursor)->fd, FALSE)) != 0)
885ca1c9b0cSelric 	return ret;
886b9d004c6Schristos     FCC_CURSOR(*cursor)->cred_start = lseek(FCC_CURSOR(*cursor)->fd,
887b9d004c6Schristos 					   0, SEEK_CUR);
888ca1c9b0cSelric 
889ca1c9b0cSelric     ret = krb5_ret_creds(FCC_CURSOR(*cursor)->sp, creds);
890ca1c9b0cSelric     if (ret)
891ca1c9b0cSelric 	krb5_clear_error_message(context);
892ca1c9b0cSelric 
893b9d004c6Schristos     FCC_CURSOR(*cursor)->cred_end = lseek(FCC_CURSOR(*cursor)->fd,
894b9d004c6Schristos 					 0, SEEK_CUR);
895b9d004c6Schristos 
896ca1c9b0cSelric     fcc_unlock(context, FCC_CURSOR(*cursor)->fd);
897ca1c9b0cSelric     return ret;
898ca1c9b0cSelric }
899ca1c9b0cSelric 
900ca1c9b0cSelric static krb5_error_code KRB5_CALLCONV
fcc_end_get(krb5_context context,krb5_ccache id,krb5_cc_cursor * cursor)901ca1c9b0cSelric fcc_end_get (krb5_context context,
902ca1c9b0cSelric 	     krb5_ccache id,
903ca1c9b0cSelric 	     krb5_cc_cursor *cursor)
904ca1c9b0cSelric {
9054f77a458Spettai 
9064f77a458Spettai     if (FCACHE(id) == NULL)
9074f77a458Spettai         return krb5_einval(context, 2);
9084f77a458Spettai 
9094f77a458Spettai     if (FCC_CURSOR(*cursor) == NULL)
9104f77a458Spettai         return krb5_einval(context, 3);
9114f77a458Spettai 
912ca1c9b0cSelric     krb5_storage_free(FCC_CURSOR(*cursor)->sp);
913ca1c9b0cSelric     close (FCC_CURSOR(*cursor)->fd);
914ca1c9b0cSelric     free(*cursor);
915ca1c9b0cSelric     *cursor = NULL;
916ca1c9b0cSelric     return 0;
917ca1c9b0cSelric }
918ca1c9b0cSelric 
919b9d004c6Schristos static void KRB5_CALLCONV
cred_delete(krb5_context context,krb5_ccache id,krb5_cc_cursor * cursor,krb5_creds * cred)920b9d004c6Schristos cred_delete(krb5_context context,
921b9d004c6Schristos 	    krb5_ccache id,
922b9d004c6Schristos 	    krb5_cc_cursor *cursor,
923b9d004c6Schristos 	    krb5_creds *cred)
924b9d004c6Schristos {
925b9d004c6Schristos     krb5_error_code ret;
926b9d004c6Schristos     krb5_storage *sp;
927b9d004c6Schristos     krb5_data orig_cred_data;
928b9d004c6Schristos     unsigned char *cred_data_in_file = NULL;
929b9d004c6Schristos     off_t new_cred_sz;
930b9d004c6Schristos     struct stat sb1, sb2;
931b9d004c6Schristos     int fd = -1;
932b9d004c6Schristos     ssize_t bytes;
933b9d004c6Schristos     krb5_const_realm srealm = krb5_principal_get_realm(context, cred->server);
934b9d004c6Schristos 
935b9d004c6Schristos     /* This is best-effort code; if we lose track of errors here it's OK */
936b9d004c6Schristos 
937b9d004c6Schristos     heim_assert(FCC_CURSOR(*cursor)->cred_start < FCC_CURSOR(*cursor)->cred_end,
938b9d004c6Schristos 		"fcache internal error");
939b9d004c6Schristos 
940b9d004c6Schristos     krb5_data_zero(&orig_cred_data);
941b9d004c6Schristos 
942b9d004c6Schristos     sp = krb5_storage_emem();
943b9d004c6Schristos     if (sp == NULL)
944b9d004c6Schristos 	return;
945b9d004c6Schristos     krb5_storage_set_eof_code(sp, KRB5_CC_END);
946b9d004c6Schristos     storage_set_flags(context, sp, FCACHE(id)->version);
947b9d004c6Schristos 
948b9d004c6Schristos     /* Get a copy of what the cred should look like in the file; see below */
949b9d004c6Schristos     ret = krb5_store_creds(sp, cred);
950b9d004c6Schristos     if (ret)
951b9d004c6Schristos 	goto out;
952b9d004c6Schristos 
953b9d004c6Schristos     ret = krb5_storage_to_data(sp, &orig_cred_data);
954b9d004c6Schristos     if (ret)
955b9d004c6Schristos 	goto out;
956b9d004c6Schristos     krb5_storage_free(sp);
957b9d004c6Schristos 
958b9d004c6Schristos     cred_data_in_file = malloc(orig_cred_data.length);
959b9d004c6Schristos     if (cred_data_in_file == NULL)
960b9d004c6Schristos 	goto out;
961b9d004c6Schristos 
962b9d004c6Schristos     /*
963b9d004c6Schristos      * Mark the cred expired; krb5_cc_retrieve_cred() callers should use
964b9d004c6Schristos      * KRB5_TC_MATCH_TIMES, so this should be good enough...
965b9d004c6Schristos      */
966b9d004c6Schristos     cred->times.endtime = 0;
967b9d004c6Schristos 
968b9d004c6Schristos     /* ...except for config creds because we don't check their endtimes */
969b9d004c6Schristos     if (srealm && strcmp(srealm, "X-CACHECONF:") == 0) {
970b9d004c6Schristos 	ret = krb5_principal_set_realm(context, cred->server, "X-RMED-CONF:");
971b9d004c6Schristos 	if (ret)
972b9d004c6Schristos 	    goto out;
973b9d004c6Schristos     }
974b9d004c6Schristos 
975b9d004c6Schristos     sp = krb5_storage_emem();
976b9d004c6Schristos     if (sp == NULL)
977b9d004c6Schristos 	goto out;
978b9d004c6Schristos     krb5_storage_set_eof_code(sp, KRB5_CC_END);
979b9d004c6Schristos     storage_set_flags(context, sp, FCACHE(id)->version);
980b9d004c6Schristos 
981b9d004c6Schristos     ret = krb5_store_creds(sp, cred);
982b9d004c6Schristos 
983b9d004c6Schristos     /* The new cred must be the same size as the old cred */
984b9d004c6Schristos     new_cred_sz = krb5_storage_seek(sp, 0, SEEK_END);
985b9d004c6Schristos     if (new_cred_sz != orig_cred_data.length || new_cred_sz !=
986b9d004c6Schristos 	(FCC_CURSOR(*cursor)->cred_end - FCC_CURSOR(*cursor)->cred_start)) {
987b9d004c6Schristos 	/* XXX This really can't happen.  Assert like above? */
988b9d004c6Schristos 	krb5_set_error_message(context, EINVAL,
989b9d004c6Schristos 			       N_("Credential deletion failed on ccache "
990b9d004c6Schristos 				  "FILE:%s: new credential size did not "
991b9d004c6Schristos 				  "match old credential size", ""),
992b9d004c6Schristos 			       FILENAME(id));
993b9d004c6Schristos 	goto out;
994b9d004c6Schristos     }
995b9d004c6Schristos 
996b9d004c6Schristos     ret = fcc_open(context, id, "remove_cred", &fd, O_RDWR, 0);
997b9d004c6Schristos     if (ret)
998b9d004c6Schristos 	goto out;
999b9d004c6Schristos 
1000b9d004c6Schristos     /*
1001b9d004c6Schristos      * Check that we're updating the same file where we got the
1002b9d004c6Schristos      * cred's offset, else we'd be corrupting a new ccache.
1003b9d004c6Schristos      */
1004b9d004c6Schristos     if (fstat(FCC_CURSOR(*cursor)->fd, &sb1) == -1 ||
1005b9d004c6Schristos 	fstat(fd, &sb2) == -1)
1006b9d004c6Schristos 	goto out;
1007b9d004c6Schristos     if (sb1.st_dev != sb2.st_dev || sb1.st_ino != sb2.st_ino)
1008b9d004c6Schristos 	goto out;
1009b9d004c6Schristos 
1010b9d004c6Schristos     /*
1011b9d004c6Schristos      * Make sure what we overwrite is what we expected.
1012b9d004c6Schristos      *
1013b9d004c6Schristos      * FIXME: We *really* need the ccache v4 tag for ccache ID.  This
1014b9d004c6Schristos      * check that we're only overwriting something that looks exactly
1015b9d004c6Schristos      * like what we want to is probably good enough in practice, but
1016b9d004c6Schristos      * it's not guaranteed to work.
1017b9d004c6Schristos      */
1018b9d004c6Schristos     if (lseek(fd, FCC_CURSOR(*cursor)->cred_start, SEEK_SET) == (off_t)-1)
1019b9d004c6Schristos 	goto out;
1020b9d004c6Schristos     bytes = read(fd, cred_data_in_file, orig_cred_data.length);
1021b9d004c6Schristos     if (bytes != orig_cred_data.length)
1022b9d004c6Schristos 	goto out;
1023b9d004c6Schristos     if (memcmp(orig_cred_data.data, cred_data_in_file, bytes) != 0)
1024b9d004c6Schristos 	goto out;
1025b9d004c6Schristos     if (lseek(fd, FCC_CURSOR(*cursor)->cred_start, SEEK_SET) == (off_t)-1)
1026b9d004c6Schristos 	goto out;
1027b9d004c6Schristos     ret = write_storage(context, sp, fd);
1028b9d004c6Schristos out:
1029b9d004c6Schristos     if (fd > -1) {
1030b9d004c6Schristos 	fcc_unlock(context, fd);
1031b9d004c6Schristos 	if (close(fd) < 0 && ret == 0) {
1032b9d004c6Schristos 	    krb5_set_error_message(context, errno, N_("close %s", ""),
1033b9d004c6Schristos 				   FILENAME(id));
1034b9d004c6Schristos 	}
1035b9d004c6Schristos     }
1036b9d004c6Schristos     krb5_data_free(&orig_cred_data);
1037b9d004c6Schristos     free(cred_data_in_file);
1038b9d004c6Schristos     krb5_storage_free(sp);
1039b9d004c6Schristos     return;
1040b9d004c6Schristos }
1041b9d004c6Schristos 
1042ca1c9b0cSelric static krb5_error_code KRB5_CALLCONV
fcc_remove_cred(krb5_context context,krb5_ccache id,krb5_flags which,krb5_creds * mcred)1043ca1c9b0cSelric fcc_remove_cred(krb5_context context,
1044ca1c9b0cSelric 		krb5_ccache id,
1045ca1c9b0cSelric 		krb5_flags which,
1046b9d004c6Schristos 		krb5_creds *mcred)
1047ca1c9b0cSelric {
1048b9d004c6Schristos     krb5_error_code ret, ret2;
1049b9d004c6Schristos     krb5_cc_cursor cursor;
1050b9d004c6Schristos     krb5_creds found_cred;
1051ca1c9b0cSelric 
10524f77a458Spettai     if (FCACHE(id) == NULL)
10534f77a458Spettai 	return krb5_einval(context, 2);
10544f77a458Spettai 
1055b9d004c6Schristos     ret = krb5_cc_start_seq_get(context, id, &cursor);
1056ca1c9b0cSelric     if (ret)
1057ca1c9b0cSelric 	return ret;
1058b9d004c6Schristos     while ((ret = krb5_cc_next_cred(context, id, &cursor, &found_cred)) == 0) {
1059b9d004c6Schristos 	if (!krb5_compare_creds(context, which, mcred, &found_cred)) {
1060b9d004c6Schristos             krb5_free_cred_contents(context, &found_cred);
1061b9d004c6Schristos 	    continue;
1062ca1c9b0cSelric         }
1063b9d004c6Schristos 	cred_delete(context, id, &cursor, &found_cred);
1064b9d004c6Schristos 	krb5_free_cred_contents(context, &found_cred);
1065ca1c9b0cSelric     }
1066b9d004c6Schristos     ret2 = krb5_cc_end_seq_get(context, id, &cursor);
1067b9d004c6Schristos     if (ret == 0)
1068b9d004c6Schristos 	return ret2;
1069b9d004c6Schristos     if (ret == KRB5_CC_END)
1070b9d004c6Schristos 	return 0;
1071ca1c9b0cSelric     return ret;
1072ca1c9b0cSelric }
1073ca1c9b0cSelric 
1074ca1c9b0cSelric static krb5_error_code KRB5_CALLCONV
fcc_set_flags(krb5_context context,krb5_ccache id,krb5_flags flags)1075ca1c9b0cSelric fcc_set_flags(krb5_context context,
1076ca1c9b0cSelric 	      krb5_ccache id,
1077ca1c9b0cSelric 	      krb5_flags flags)
1078ca1c9b0cSelric {
10794f77a458Spettai     if (FCACHE(id) == NULL)
10804f77a458Spettai         return krb5_einval(context, 2);
10814f77a458Spettai 
1082ca1c9b0cSelric     return 0; /* XXX */
1083ca1c9b0cSelric }
1084ca1c9b0cSelric 
1085ca1c9b0cSelric static int KRB5_CALLCONV
fcc_get_version(krb5_context context,krb5_ccache id)1086ca1c9b0cSelric fcc_get_version(krb5_context context,
1087ca1c9b0cSelric 		krb5_ccache id)
1088ca1c9b0cSelric {
10894f77a458Spettai     if (FCACHE(id) == NULL)
10904f77a458Spettai         return -1;
10914f77a458Spettai 
1092ca1c9b0cSelric     return FCACHE(id)->version;
1093ca1c9b0cSelric }
1094ca1c9b0cSelric 
1095ca1c9b0cSelric struct fcache_iter {
1096ca1c9b0cSelric     int first;
1097ca1c9b0cSelric };
1098ca1c9b0cSelric 
1099ca1c9b0cSelric static krb5_error_code KRB5_CALLCONV
fcc_get_cache_first(krb5_context context,krb5_cc_cursor * cursor)1100ca1c9b0cSelric fcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
1101ca1c9b0cSelric {
1102ca1c9b0cSelric     struct fcache_iter *iter;
1103ca1c9b0cSelric 
1104ca1c9b0cSelric     iter = calloc(1, sizeof(*iter));
1105ca1c9b0cSelric     if (iter == NULL) {
1106ca1c9b0cSelric 	krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));
1107ca1c9b0cSelric 	return ENOMEM;
1108ca1c9b0cSelric     }
1109ca1c9b0cSelric     iter->first = 1;
1110ca1c9b0cSelric     *cursor = iter;
1111ca1c9b0cSelric     return 0;
1112ca1c9b0cSelric }
1113ca1c9b0cSelric 
1114ca1c9b0cSelric static krb5_error_code KRB5_CALLCONV
fcc_get_cache_next(krb5_context context,krb5_cc_cursor cursor,krb5_ccache * id)1115ca1c9b0cSelric fcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
1116ca1c9b0cSelric {
1117ca1c9b0cSelric     struct fcache_iter *iter = cursor;
1118ca1c9b0cSelric     krb5_error_code ret;
1119b9d004c6Schristos     const char *fn, *cc_type;
1120b9d004c6Schristos     krb5_ccache cc;
1121ca1c9b0cSelric 
11224f77a458Spettai     if (iter == NULL)
11234f77a458Spettai         return krb5_einval(context, 2);
11244f77a458Spettai 
1125ca1c9b0cSelric     if (!iter->first) {
1126ca1c9b0cSelric 	krb5_clear_error_message(context);
1127ca1c9b0cSelric 	return KRB5_CC_END;
1128ca1c9b0cSelric     }
1129ca1c9b0cSelric     iter->first = 0;
1130ca1c9b0cSelric 
1131b9d004c6Schristos     /*
1132b9d004c6Schristos      * Note: do not allow krb5_cc_default_name() to recurse via
1133b9d004c6Schristos      * krb5_cc_cache_match().
1134b9d004c6Schristos      * Note that context->default_cc_name will be NULL even though
1135b9d004c6Schristos      * KRB5CCNAME is set in the environment if
1136b9d004c6Schristos      * krb5_cc_set_default_name() hasn't
1137b9d004c6Schristos      */
1138ca1c9b0cSelric     fn = krb5_cc_default_name(context);
1139b9d004c6Schristos     ret = krb5_cc_resolve(context, fn, &cc);
1140b9d004c6Schristos     if (ret != 0)
1141ca1c9b0cSelric         return ret;
1142b9d004c6Schristos     cc_type = krb5_cc_get_type(context, cc);
1143b9d004c6Schristos     if (strcmp(cc_type, "FILE") != 0) {
1144b9d004c6Schristos         krb5_cc_close(context, cc);
1145b9d004c6Schristos         return KRB5_CC_END;
1146ca1c9b0cSelric     }
1147ca1c9b0cSelric 
1148b9d004c6Schristos     *id = cc;
1149b9d004c6Schristos 
1150b9d004c6Schristos     return 0;
1151ca1c9b0cSelric }
1152ca1c9b0cSelric 
1153ca1c9b0cSelric static krb5_error_code KRB5_CALLCONV
fcc_end_cache_get(krb5_context context,krb5_cc_cursor cursor)1154ca1c9b0cSelric fcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
1155ca1c9b0cSelric {
1156ca1c9b0cSelric     struct fcache_iter *iter = cursor;
11574f77a458Spettai 
11584f77a458Spettai     if (iter == NULL)
11594f77a458Spettai         return krb5_einval(context, 2);
11604f77a458Spettai 
1161ca1c9b0cSelric     free(iter);
1162ca1c9b0cSelric     return 0;
1163ca1c9b0cSelric }
1164ca1c9b0cSelric 
1165ca1c9b0cSelric static krb5_error_code KRB5_CALLCONV
fcc_move(krb5_context context,krb5_ccache from,krb5_ccache to)1166ca1c9b0cSelric fcc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
1167ca1c9b0cSelric {
1168ca1c9b0cSelric     krb5_error_code ret = 0;
1169ca1c9b0cSelric 
1170ca1c9b0cSelric     ret = rk_rename(FILENAME(from), FILENAME(to));
1171ca1c9b0cSelric 
1172ca1c9b0cSelric     if (ret && errno != EXDEV) {
1173ca1c9b0cSelric 	char buf[128];
1174ca1c9b0cSelric 	ret = errno;
1175ca1c9b0cSelric 	rk_strerror_r(ret, buf, sizeof(buf));
1176ca1c9b0cSelric 	krb5_set_error_message(context, ret,
1177ca1c9b0cSelric 			       N_("Rename of file from %s "
1178ca1c9b0cSelric 				  "to %s failed: %s", ""),
1179ca1c9b0cSelric 			       FILENAME(from), FILENAME(to), buf);
1180ca1c9b0cSelric 	return ret;
1181ca1c9b0cSelric     } else if (ret && errno == EXDEV) {
1182ca1c9b0cSelric 	/* make a copy and delete the orignal */
1183ca1c9b0cSelric 	krb5_ssize_t sz1, sz2;
1184ca1c9b0cSelric 	int fd1, fd2;
1185ca1c9b0cSelric 	char buf[BUFSIZ];
1186ca1c9b0cSelric 
1187b9d004c6Schristos 	ret = fcc_open(context, from, "move/from", &fd1, O_RDONLY, 0);
1188ca1c9b0cSelric 	if(ret)
1189ca1c9b0cSelric 	    return ret;
1190ca1c9b0cSelric 
1191ca1c9b0cSelric 	unlink(FILENAME(to));
1192ca1c9b0cSelric 
1193b9d004c6Schristos 	ret = fcc_open(context, to, "move/to", &fd2,
1194b9d004c6Schristos 		       O_WRONLY | O_CREAT | O_EXCL, 0600);
1195ca1c9b0cSelric 	if(ret)
1196ca1c9b0cSelric 	    goto out1;
1197ca1c9b0cSelric 
1198ca1c9b0cSelric 	while((sz1 = read(fd1, buf, sizeof(buf))) > 0) {
1199ca1c9b0cSelric 	    sz2 = write(fd2, buf, sz1);
1200ca1c9b0cSelric 	    if (sz1 != sz2) {
1201ca1c9b0cSelric 		ret = EIO;
1202ca1c9b0cSelric 		krb5_set_error_message(context, ret,
1203ca1c9b0cSelric 				       N_("Failed to write data from one file "
1204ca1c9b0cSelric 					  "credential cache to the other", ""));
1205ca1c9b0cSelric 		goto out2;
1206ca1c9b0cSelric 	    }
1207ca1c9b0cSelric 	}
1208ca1c9b0cSelric 	if (sz1 < 0) {
1209ca1c9b0cSelric 	    ret = EIO;
1210ca1c9b0cSelric 	    krb5_set_error_message(context, ret,
1211ca1c9b0cSelric 				   N_("Failed to read data from one file "
1212ca1c9b0cSelric 				      "credential cache to the other", ""));
1213ca1c9b0cSelric 	    goto out2;
1214ca1c9b0cSelric 	}
1215ca1c9b0cSelric     out2:
1216ca1c9b0cSelric 	fcc_unlock(context, fd2);
1217ca1c9b0cSelric 	close(fd2);
1218ca1c9b0cSelric 
1219ca1c9b0cSelric     out1:
1220ca1c9b0cSelric 	fcc_unlock(context, fd1);
1221ca1c9b0cSelric 	close(fd1);
1222ca1c9b0cSelric 
1223ca1c9b0cSelric 	_krb5_erase_file(context, FILENAME(from));
1224ca1c9b0cSelric 
1225ca1c9b0cSelric 	if (ret) {
1226ca1c9b0cSelric 	    _krb5_erase_file(context, FILENAME(to));
1227ca1c9b0cSelric 	    return ret;
1228ca1c9b0cSelric 	}
1229ca1c9b0cSelric     }
1230ca1c9b0cSelric 
1231ca1c9b0cSelric     /* make sure ->version is uptodate */
1232ca1c9b0cSelric     {
1233ca1c9b0cSelric 	krb5_storage *sp;
1234ca1c9b0cSelric 	int fd;
1235b9d004c6Schristos 	if ((ret = init_fcc (context, to, "move", &sp, &fd, NULL)) == 0) {
1236ca1c9b0cSelric 	    if (sp)
1237ca1c9b0cSelric 		krb5_storage_free(sp);
1238ca1c9b0cSelric 	    fcc_unlock(context, fd);
1239ca1c9b0cSelric 	    close(fd);
1240ca1c9b0cSelric 	}
1241ca1c9b0cSelric     }
1242ca1c9b0cSelric 
1243ca1c9b0cSelric     fcc_close(context, from);
1244ca1c9b0cSelric 
1245ca1c9b0cSelric     return ret;
1246ca1c9b0cSelric }
1247ca1c9b0cSelric 
1248ca1c9b0cSelric static krb5_error_code KRB5_CALLCONV
fcc_get_default_name(krb5_context context,char ** str)1249ca1c9b0cSelric fcc_get_default_name(krb5_context context, char **str)
1250ca1c9b0cSelric {
1251ca1c9b0cSelric     return _krb5_expand_default_cc_name(context,
1252ca1c9b0cSelric 					KRB5_DEFAULT_CCNAME_FILE,
1253ca1c9b0cSelric 					str);
1254ca1c9b0cSelric }
1255ca1c9b0cSelric 
1256ca1c9b0cSelric static krb5_error_code KRB5_CALLCONV
fcc_lastchange(krb5_context context,krb5_ccache id,krb5_timestamp * mtime)1257ca1c9b0cSelric fcc_lastchange(krb5_context context, krb5_ccache id, krb5_timestamp *mtime)
1258ca1c9b0cSelric {
1259ca1c9b0cSelric     krb5_error_code ret;
1260ca1c9b0cSelric     struct stat sb;
1261ca1c9b0cSelric     int fd;
1262ca1c9b0cSelric 
1263b9d004c6Schristos     ret = fcc_open(context, id, "lastchange", &fd, O_RDONLY, 0);
1264ca1c9b0cSelric     if(ret)
1265ca1c9b0cSelric 	return ret;
1266ca1c9b0cSelric     ret = fstat(fd, &sb);
1267ca1c9b0cSelric     close(fd);
1268ca1c9b0cSelric     if (ret) {
1269ca1c9b0cSelric 	ret = errno;
1270ca1c9b0cSelric 	krb5_set_error_message(context, ret, N_("Failed to stat cache file", ""));
1271ca1c9b0cSelric 	return ret;
1272ca1c9b0cSelric     }
1273ca1c9b0cSelric     *mtime = sb.st_mtime;
1274ca1c9b0cSelric     return 0;
1275ca1c9b0cSelric }
1276ca1c9b0cSelric 
1277ca1c9b0cSelric static krb5_error_code KRB5_CALLCONV
fcc_set_kdc_offset(krb5_context context,krb5_ccache id,krb5_deltat kdc_offset)1278ca1c9b0cSelric fcc_set_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat kdc_offset)
1279ca1c9b0cSelric {
1280ca1c9b0cSelric     return 0;
1281ca1c9b0cSelric }
1282ca1c9b0cSelric 
1283ca1c9b0cSelric static krb5_error_code KRB5_CALLCONV
fcc_get_kdc_offset(krb5_context context,krb5_ccache id,krb5_deltat * kdc_offset)1284ca1c9b0cSelric fcc_get_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat *kdc_offset)
1285ca1c9b0cSelric {
1286ca1c9b0cSelric     krb5_error_code ret;
1287ca1c9b0cSelric     krb5_storage *sp = NULL;
1288ca1c9b0cSelric     int fd;
1289b9d004c6Schristos     ret = init_fcc(context, id, "get-kdc-offset", &sp, &fd, kdc_offset);
1290ca1c9b0cSelric     if (sp)
1291ca1c9b0cSelric 	krb5_storage_free(sp);
1292ca1c9b0cSelric     fcc_unlock(context, fd);
1293ca1c9b0cSelric     close(fd);
1294ca1c9b0cSelric 
1295ca1c9b0cSelric     return ret;
1296ca1c9b0cSelric }
1297ca1c9b0cSelric 
1298ca1c9b0cSelric 
1299ca1c9b0cSelric /**
1300ca1c9b0cSelric  * Variable containing the FILE based credential cache implemention.
1301ca1c9b0cSelric  *
1302ca1c9b0cSelric  * @ingroup krb5_ccache
1303ca1c9b0cSelric  */
1304ca1c9b0cSelric 
1305ca1c9b0cSelric KRB5_LIB_VARIABLE const krb5_cc_ops krb5_fcc_ops = {
1306ca1c9b0cSelric     KRB5_CC_OPS_VERSION,
1307ca1c9b0cSelric     "FILE",
1308ca1c9b0cSelric     fcc_get_name,
1309ca1c9b0cSelric     fcc_resolve,
1310ca1c9b0cSelric     fcc_gen_new,
1311ca1c9b0cSelric     fcc_initialize,
1312ca1c9b0cSelric     fcc_destroy,
1313ca1c9b0cSelric     fcc_close,
1314ca1c9b0cSelric     fcc_store_cred,
1315ca1c9b0cSelric     NULL, /* fcc_retrieve */
1316ca1c9b0cSelric     fcc_get_principal,
1317ca1c9b0cSelric     fcc_get_first,
1318ca1c9b0cSelric     fcc_get_next,
1319ca1c9b0cSelric     fcc_end_get,
1320ca1c9b0cSelric     fcc_remove_cred,
1321ca1c9b0cSelric     fcc_set_flags,
1322ca1c9b0cSelric     fcc_get_version,
1323ca1c9b0cSelric     fcc_get_cache_first,
1324ca1c9b0cSelric     fcc_get_cache_next,
1325ca1c9b0cSelric     fcc_end_cache_get,
1326ca1c9b0cSelric     fcc_move,
1327ca1c9b0cSelric     fcc_get_default_name,
1328ca1c9b0cSelric     NULL,
1329ca1c9b0cSelric     fcc_lastchange,
1330ca1c9b0cSelric     fcc_set_kdc_offset,
1331ca1c9b0cSelric     fcc_get_kdc_offset
1332ca1c9b0cSelric };
1333