1 /* file.c --- File based Shisa database.
2 * Copyright (C) 2002, 2003, 2004 Simon Josefsson
4 * This file is part of Shishi.
6 * Shishi is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * Shishi is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with Shishi; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 * Theory of operation:
25 * Data is stored in the standard file system, so it is subject to
26 * normal access permission infrastructure, e.g. POSIX ACL or normal
27 * Unix file permissions. A definition of the file database looks
30 * file LOCATION OPTIONS
32 * Where LOCATION is a path name, e.g. /var/shisa. No OPTIONS are
33 * currently implemented.
35 * Realms are directories in LOCATION. Principals are directories in
36 * realm directories. Characters outside A-Za-z0-9_- are escaped
37 * using the URL encoding, e.g. example/host%2fwww denote the
38 * "host/www" principal in the "example" realm.
42 * LOCATION/EXAMPLE.ORG
43 * LOCATION/EXAMPLE.ORG/krbtgt%2fEXAMPLE.ORG
44 * LOCATION/EXAMPLE.ORG/host%2fwww.example.org
45 * LOCATION/EXAMPLE.NET
46 * LOCATION/EXAMPLE.NET/krbtgt%2fEXAMPLE.NET
50 /* XXX fix race conditions. */
57 /* Get low-level file utilities. */
66 typedef struct Shisa_file Shisa_file
;
71 ALLOW_CREATE_OPTION
= 1,
75 static const char *_shisa_file_opts
[] = {
76 /* [READ_ONLY_OPTION] = */ "read-only",
77 /* [ALLOW_CREATE_OPTION] = */ "allow-create",
78 /* [THE_END] = */ NULL
82 shisa_file_cfg (Shisa
* dbh
, Shisa_file
* info
, const char *option
)
84 char *opt
= option
? xstrdup (option
) : NULL
;
88 while (p
!= NULL
&& *p
!= '\0')
90 switch (getsubopt (&p
, _shisa_file_opts
, &value
))
92 case READ_ONLY_OPTION
:
96 case ALLOW_CREATE_OPTION
:
97 info
->allowcreate
= 1;
101 shisa_info (dbh
, "Unknown file database option: `%s'.", value
);
102 return SHISA_CFG_SYNTAX_ERROR
;
113 /* Initialize file backend, i.e., parse options and check if file root
114 exists and allocate backend handle. */
116 shisa_file_init (Shisa
* dbh
,
117 const char *location
, const char *options
, void **state
)
122 if (!_shisa_isdir (location
))
123 return SHISA_OPEN_ERROR
;
125 *state
= info
= xcalloc (1, sizeof (*info
));
127 rc
= shisa_file_cfg (dbh
, info
, options
);
131 info
->path
= xstrdup (location
);
136 /* Destroy backend handle. */
138 shisa_file_done (Shisa
* dbh
, void *state
)
140 Shisa_file
*info
= state
;
150 /* Return a list of all realm names in backend, as zero-terminated
151 UTF-8 strings. The caller must deallocate the strings. */
153 shisa_file_enumerate_realms (Shisa
* dbh
,
154 void *state
, char ***realms
, size_t * nrealms
)
156 Shisa_file
*info
= state
;
158 if (_shisa_lsdir (info
->path
, realms
, nrealms
) != 0)
159 return SHISA_ENUMERATE_REALM_ERROR
;
164 /* Return a list of all principals in realm in backend, as
165 zero-terminated UTF-8 strings. The caller must deallocate the
168 shisa_file_enumerate_principals (Shisa
* dbh
,
171 char ***principals
, size_t * nprincipals
)
173 Shisa_file
*info
= state
;
175 if (!_shisa_isdir2 (info
->path
, realm
))
176 return SHISA_NO_REALM
;
178 if (_shisa_lsdir2 (info
->path
, realm
, principals
, nprincipals
) != 0)
179 return SHISA_ENUMERATE_PRINCIPAL_ERROR
;
184 /* Return information about specified PRINCIPAL@REALM. Can also be
185 used check existence of principal entry, with a NULL PH. */
187 shisa_file_principal_find (Shisa
* dbh
,
190 const char *principal
, Shisa_principal
* ph
)
192 Shisa_file
*info
= state
;
194 if (!_shisa_isdir3 (info
->path
, realm
, principal
))
195 return SHISA_NO_PRINCIPAL
;
201 _shisa_mtime4 (info
->path
, realm
, principal
, "validfrom.stamp");
203 _shisa_isfile4 (info
->path
, realm
, principal
, "disabled.flag");
204 ph
->kvno
= _shisa_uint32link4 (info
->path
, realm
, principal
, "latest.key");
206 _shisa_mtime4 (info
->path
, realm
, principal
, "lastinitaltgt.stamp");
207 ph
->lastinitialrequest
=
208 _shisa_mtime4 (info
->path
, realm
, principal
, "lastinitial.stamp");
209 ph
->lasttgt
= _shisa_mtime4 (info
->path
, realm
, principal
, "lasttgt.stamp");
211 _shisa_mtime4 (info
->path
, realm
, principal
, "lastrenewal.stamp");
213 _shisa_mtime4 (info
->path
, realm
, principal
, "passwordexpire.stamp");
215 _shisa_mtime4 (info
->path
, realm
, principal
, "accountexpire.stamp");
221 realm_add (Shisa
* dbh
, void *state
, const char *realm
)
223 Shisa_file
*info
= state
;
225 if (_shisa_isdir2 (info
->path
, realm
))
226 return SHISA_ADD_REALM_EXISTS
;
228 if (_shisa_mkdir2 (info
->path
, realm
) != 0)
229 return SHISA_ADD_REALM_ERROR
;
236 principal_add (Shisa
* dbh
,
239 const char *principal
,
240 const Shisa_principal
* ph
, const Shisa_key
* key
)
242 Shisa_file
*info
= state
;
244 if (!_shisa_isdir2 (info
->path
, realm
))
245 return SHISA_NO_REALM
;
247 if (_shisa_isdir3 (info
->path
, realm
, principal
))
248 return SHISA_ADD_PRINCIPAL_EXISTS
;
250 if (_shisa_mkdir3 (info
->path
, realm
, principal
) != 0)
251 return SHISA_ADD_PRINCIPAL_ERROR
;
254 shisa_file_principal_update (dbh
, state
, realm
, principal
, ph
);
257 shisa_file_key_add (dbh
, state
, realm
, principal
, key
);
262 /* Add new PRINCIPAL@REALM with specified information and key. If
263 PRINCIPAL is NULL, then add realm REALM. */
265 shisa_file_principal_add (Shisa
* dbh
,
268 const char *principal
,
269 const Shisa_principal
* ph
, const Shisa_key
* key
)
273 if (principal
== NULL
)
274 rc
= realm_add (dbh
, state
, realm
);
276 rc
= principal_add (dbh
, state
, realm
, principal
, ph
, key
);
281 /* Modify information for specified PRINCIPAL@REALM. */
283 shisa_file_principal_update (Shisa
* dbh
,
286 const char *principal
,
287 const Shisa_principal
* ph
)
289 Shisa_file
*info
= state
;
295 realm_remove (Shisa
* dbh
, void *state
, const char *realm
)
297 Shisa_file
*info
= state
;
298 size_t nprincipals
= 0;
301 if (!_shisa_isdir2 (info
->path
, realm
))
302 return SHISA_NO_REALM
;
305 shisa_file_enumerate_principals (dbh
, state
, realm
, NULL
, &nprincipals
);
310 return SHISA_REMOVE_REALM_NONEMPTY
;
312 if (_shisa_rmdir2 (info
->path
, realm
) != 0)
313 return SHISA_REMOVE_REALM_ERROR
;
319 remove_keys (Shisa
* dbh
,
320 void *state
, const char *realm
, const char *principal
)
322 Shisa_file
*info
= state
;
331 rc
= _shisa_ls4 (info
->path
, realm
, principal
, "keys", &files
, &nfiles
);
335 for (i
= 0; i
< nfiles
; i
++)
337 rc
= _shisa_rm5 (info
->path
, realm
, principal
, "keys", files
[i
]);
342 rc
= _shisa_rmdir4 (info
->path
, realm
, principal
, "keys");
351 remove_info (Shisa
* dbh
,
352 void *state
, const char *realm
, const char *principal
)
354 Shisa_file
*info
= state
;
363 rc
= _shisa_ls3 (info
->path
, realm
, principal
, &files
, &nfiles
);
367 for (i
= 0; i
< nfiles
; i
++)
369 rc
= _shisa_rm4 (info
->path
, realm
, principal
, files
[i
]);
379 principal_remove (Shisa
* dbh
,
380 void *state
, const char *realm
, const char *principal
)
382 Shisa_file
*info
= state
;
385 if (!_shisa_isdir2 (info
->path
, realm
))
386 return SHISA_NO_REALM
;
388 if (!_shisa_isdir3 (info
->path
, realm
, principal
))
389 return SHISA_NO_PRINCIPAL
;
391 rc
= remove_keys (dbh
, state
, realm
, principal
);
395 if (_shisa_rmdir3 (info
->path
, realm
, principal
) != 0)
396 return SHISA_REMOVE_PRINCIPAL_ERROR
;
401 /* Remove PRINCIPAL@REALM, or REALM if PRINCIPAL is NULL. Realms must
402 be empty for them to be successfully removed. */
404 shisa_file_principal_remove (Shisa
* dbh
,
406 const char *realm
, const char *principal
)
410 if (principal
== NULL
)
411 rc
= realm_remove (dbh
, state
, realm
);
413 rc
= principal_remove (dbh
, state
, realm
, principal
);
419 read_key (Shisa
* dbh
,
422 const char *principal
, const char *keyfile
, Shisa_key
** key
)
431 asprintf (&file
, "keys/%s", keyfile
);
432 fh
= _shisa_fopen4 (info
->path
, realm
, principal
, file
, "r");
437 memset (&tmpkey
, 0, sizeof (tmpkey
));
439 rc
= fscanf (fh
, "%u %u %u %u %u %u", &tmpkey
.etype
, &tmpkey
.keylen
,
440 &tmpkey
.saltlen
, &tmpkey
.str2keyparamlen
, &passwdlen
,
442 if (rc
!= 5 && rc
!= 6)
449 /* We can't include '\n' in scanf format above, because any
450 whitespace on the next line will be skipped. */
451 if (fread (&junk
, 1, 1, fh
) != 1 || junk
!= '\n')
454 if (tmpkey
.keylen
> 0)
456 tmpkey
.key
= xmalloc (tmpkey
.keylen
+ 1);
457 if (fread (tmpkey
.key
, 1, tmpkey
.keylen
, fh
) != tmpkey
.keylen
)
459 tmpkey
.key
[tmpkey
.keylen
] = '\0';
462 if (tmpkey
.saltlen
> 0)
464 tmpkey
.salt
= xmalloc (tmpkey
.saltlen
+ 1);
465 if (fread (tmpkey
.salt
, 1, tmpkey
.saltlen
, fh
) != tmpkey
.saltlen
)
467 tmpkey
.salt
[tmpkey
.saltlen
] = '\0';
470 if (tmpkey
.str2keyparamlen
> 0)
472 tmpkey
.str2keyparam
= xmalloc (tmpkey
.str2keyparamlen
+ 1);
473 if (fread (tmpkey
.str2keyparam
, 1, tmpkey
.str2keyparamlen
, fh
) !=
474 tmpkey
.str2keyparamlen
)
476 tmpkey
.str2keyparam
[tmpkey
.str2keyparamlen
] = '\0';
481 tmpkey
.password
= xmalloc (passwdlen
+ 1);
482 if (fread (tmpkey
.password
, 1, passwdlen
, fh
) != passwdlen
)
484 tmpkey
.password
[passwdlen
] = '\0';
494 *key
= xmalloc (sizeof (**key
));
495 memcpy (*key
, &tmpkey
, sizeof (tmpkey
));
501 key_match (const Shisa_key
* hint
, Shisa_key
* key
)
506 ok
= ok
&& hint
->kvno
== key
->kvno
;
508 ok
= ok
&& hint
->etype
== key
->etype
;
510 ok
= ok
&& hint
->keylen
== key
->keylen
&&
511 memcmp (hint
->key
, key
->key
, key
->keylen
) == 0;
513 ok
= ok
&& hint
->saltlen
== key
->saltlen
&&
514 memcmp (hint
->salt
, key
->salt
, key
->saltlen
) == 0;
515 if (hint
->str2keyparamlen
)
516 ok
= ok
&& hint
->str2keyparamlen
== key
->str2keyparamlen
&&
517 memcmp (hint
->str2keyparam
, key
->str2keyparam
,
518 key
->str2keyparamlen
) == 0;
520 ok
= ok
&& strcmp (hint
->password
, key
->password
) == 0;
525 /* Get all keys matching HINT for specified PRINCIPAL@REALM. The
526 caller must deallocate the returned keys. If HINT is NULL, then
527 all keys are returned. */
529 shisa_file_keys_find (Shisa
* dbh
,
532 const char *principal
,
533 const Shisa_key
* hint
,
534 Shisa_key
*** keys
, size_t * nkeys
)
536 Shisa_file
*info
= state
;
539 size_t nfiles
, matched
= 0;
546 rc
= _shisa_ls4 (info
->path
, realm
, principal
, "keys", &files
, &nfiles
);
548 return SHISA_ENUMERATE_KEY_ERROR
;
553 *keys
= xmalloc (nfiles
* sizeof (**keys
));
554 for (i
= 0; i
< nfiles
; i
++)
556 if (rc
== SHISA_OK
&&
557 (rc
= read_key (dbh
, info
, realm
, principal
,
558 files
[i
], &tmpkey
)) == SHISA_OK
)
560 if (hint
== NULL
|| key_match (hint
, tmpkey
))
563 (*keys
)[matched
] = tmpkey
;
567 shisa_key_free (dbh
, tmpkey
);
581 /* Add key for PRINCIPAL@REALM. */
583 shisa_file_key_add (Shisa
* dbh
,
586 const char *principal
, const Shisa_key
* key
)
588 Shisa_file
*info
= state
;
589 size_t passwdlen
= key
&& key
->password
? strlen (key
->password
) : 0;
597 if (!_shisa_isdir4 (info
->path
, realm
, principal
, "keys") &&
598 _shisa_mkdir4 (info
->path
, realm
, principal
, "keys"))
605 asprintf (&file
, "keys/%d-%d-%d.key", key
->kvno
, key
->etype
, num
++);
607 while (_shisa_isfile4 (info
->path
, realm
, principal
, file
));
608 fh
= _shisa_fopen4 (info
->path
, realm
, principal
, file
, "w");
613 return SHISA_ADD_KEY_ERROR
;
616 fprintf (fh
, "%u %u %u %u %u %u\n", key
->etype
, key
->keylen
,
617 key
->saltlen
, key
->str2keyparamlen
, passwdlen
, key
->priority
);
619 fwrite (key
->key
, 1, key
->keylen
, fh
);
620 if (key
->saltlen
> 0)
621 fwrite (key
->salt
, 1, key
->saltlen
, fh
);
622 if (key
->str2keyparamlen
> 0)
623 fwrite (key
->str2keyparam
, 1, key
->str2keyparamlen
, fh
);
625 fwrite (key
->password
, 1, passwdlen
, fh
);
632 /* Update a key for PRINCIPAL@REALM. The OLDKEY must uniquely
633 determine the key to update, i.e., shishi_keys_find using OLDKEY as
634 HINT must return exactly 1 key. */
636 shisa_file_key_update (Shisa
* dbh
,
639 const char *principal
,
640 const Shisa_key
* oldkey
, const Shisa_key
* newkey
)
642 Shisa_file
*info
= state
;
649 /* Remove a key for PRINCIPAL@REALM. The KEY must uniquely determine
650 the key to remove, i.e., shishi_keys_find using KEY as HINT must
651 return exactly 1 key. */
653 shisa_file_key_remove (Shisa
* dbh
,
656 const char *principal
, const Shisa_key
* key
)
658 Shisa_file
*info
= state
;
669 rc
= _shisa_ls4 (info
->path
, realm
, principal
, "keys", &files
, &nfiles
);
673 for (i
= 0; i
< nfiles
; i
++)
675 if (rc
== SHISA_OK
&&
676 (rc
= read_key (dbh
, info
, realm
, principal
,
677 files
[i
], &tmpkey
)) == SHISA_OK
)
679 if (key
== NULL
|| key_match (key
, tmpkey
))
684 rc
= SHISA_MULTIPLE_KEY_MATCH
;
687 found
= xstrdup (files
[i
]);
689 shisa_key_free (dbh
, tmpkey
);
703 rc
= _shisa_rm5 (info
->path
, realm
, principal
, "keys", found
);