Change GPLv2+ to GPLv3+.
[shishi.git] / db / file.c
blob6fd2c92bb31dedc7d255a7a6c87a5a9a6756ff83
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 3 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
28 * like:
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.
40 * Example file tree:
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. */
52 #include "info.h"
54 /* Get prototypes. */
55 #include "file.h"
57 /* Get low-level file utilities. */
58 #include "fileutil.h"
60 struct Shisa_file
62 char *path;
63 int readonly;
64 int allowcreate;
66 typedef struct Shisa_file Shisa_file;
68 enum
70 READ_ONLY_OPTION = 0,
71 ALLOW_CREATE_OPTION = 1,
72 THE_END
75 static const char *_shisa_file_opts[] = {
76 /* [READ_ONLY_OPTION] = */ "read-only",
77 /* [ALLOW_CREATE_OPTION] = */ "allow-create",
78 /* [THE_END] = */ NULL
81 static int
82 shisa_file_cfg (Shisa * dbh, Shisa_file * info, const char *option)
84 char *opt = option ? xstrdup (option) : NULL;
85 char *p = opt;
86 char *value;
88 while (p != NULL && *p != '\0')
90 switch (getsubopt (&p, _shisa_file_opts, &value))
92 case READ_ONLY_OPTION:
93 info->readonly = 1;
94 break;
96 case ALLOW_CREATE_OPTION:
97 info->allowcreate = 1;
98 break;
100 default:
101 shisa_info (dbh, "Unknown file database option: `%s'.", value);
102 return SHISA_CFG_SYNTAX_ERROR;
103 break;
107 if (opt)
108 free (opt);
110 return SHISA_OK;
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)
119 Shisa_file *info;
120 int rc;
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);
128 if (rc != SHISA_OK)
129 return rc;
131 info->path = xstrdup (location);
133 return SHISA_OK;
136 /* Destroy backend handle. */
137 void
138 shisa_file_done (Shisa * dbh, void *state)
140 Shisa_file *info = state;
142 if (info)
144 if (info->path)
145 free (info->path);
146 free (info);
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;
161 return SHISA_OK;
164 /* Return a list of all principals in realm in backend, as
165 zero-terminated UTF-8 strings. The caller must deallocate the
166 strings. */
168 shisa_file_enumerate_principals (Shisa * dbh,
169 void *state,
170 const char *realm,
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;
181 return SHISA_OK;
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,
188 void *state,
189 const char *realm,
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;
197 if (!ph)
198 return SHISA_OK;
200 ph->notusedbefore =
201 _shisa_mtime4 (info->path, realm, principal, "validfrom.stamp");
202 ph->isdisabled =
203 _shisa_isfile4 (info->path, realm, principal, "disabled.flag");
204 ph->kvno = _shisa_uint32link4 (info->path, realm, principal, "latest.key");
205 ph->lastinitialtgt =
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");
210 ph->lastrenewal =
211 _shisa_mtime4 (info->path, realm, principal, "lastrenewal.stamp");
212 ph->passwordexpire =
213 _shisa_mtime4 (info->path, realm, principal, "passwordexpire.stamp");
214 ph->accountexpire =
215 _shisa_mtime4 (info->path, realm, principal, "accountexpire.stamp");
217 return SHISA_OK;
220 static int
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;
231 return SHISA_OK;
235 static int
236 principal_add (Shisa * dbh,
237 void *state,
238 const char *realm,
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;
253 if (ph)
254 shisa_file_principal_update (dbh, state, realm, principal, ph);
256 if (key)
257 shisa_file_key_add (dbh, state, realm, principal, key);
259 return SHISA_OK;
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,
266 void *state,
267 const char *realm,
268 const char *principal,
269 const Shisa_principal * ph, const Shisa_key * key)
271 int rc;
273 if (principal == NULL)
274 rc = realm_add (dbh, state, realm);
275 else
276 rc = principal_add (dbh, state, realm, principal, ph, key);
278 return rc;
281 /* Modify information for specified PRINCIPAL@REALM. */
283 shisa_file_principal_update (Shisa * dbh,
284 void *state,
285 const char *realm,
286 const char *principal,
287 const Shisa_principal * ph)
289 Shisa_file *info = state;
291 return SHISA_OK;
294 static int
295 realm_remove (Shisa * dbh, void *state, const char *realm)
297 Shisa_file *info = state;
298 size_t nprincipals = 0;
299 int rc;
301 if (!_shisa_isdir2 (info->path, realm))
302 return SHISA_NO_REALM;
304 rc =
305 shisa_file_enumerate_principals (dbh, state, realm, NULL, &nprincipals);
306 if (rc != SHISA_OK)
307 return rc;
309 if (nprincipals > 0)
310 return SHISA_REMOVE_REALM_NONEMPTY;
312 if (_shisa_rmdir2 (info->path, realm) != 0)
313 return SHISA_REMOVE_REALM_ERROR;
315 return SHISA_OK;
318 static int
319 remove_keys (Shisa * dbh,
320 void *state, const char *realm, const char *principal)
322 Shisa_file *info = state;
323 char **files;
324 size_t nfiles;
325 size_t i;
326 int rc;
328 files = NULL;
329 nfiles = 0;
331 rc = _shisa_ls4 (info->path, realm, principal, "keys", &files, &nfiles);
332 if (rc != SHISA_OK)
333 return rc;
335 for (i = 0; i < nfiles; i++)
337 rc = _shisa_rm5 (info->path, realm, principal, "keys", files[i]);
338 free (files[i]);
340 free (files);
342 rc = _shisa_rmdir4 (info->path, realm, principal, "keys");
343 if (rc != SHISA_OK)
344 return rc;
346 return SHISA_OK;
349 #if 0
350 static int
351 remove_info (Shisa * dbh,
352 void *state, const char *realm, const char *principal)
354 Shisa_file *info = state;
355 char **files;
356 size_t nfiles;
357 size_t i;
358 int rc;
360 files = NULL;
361 nfiles = 0;
363 rc = _shisa_ls3 (info->path, realm, principal, &files, &nfiles);
364 if (rc != SHISA_OK)
365 return rc;
367 for (i = 0; i < nfiles; i++)
369 rc = _shisa_rm4 (info->path, realm, principal, files[i]);
370 free (files[i]);
372 free (files);
374 return SHISA_OK;
376 #endif
378 static int
379 principal_remove (Shisa * dbh,
380 void *state, const char *realm, const char *principal)
382 Shisa_file *info = state;
383 int rc;
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);
392 if (rc != SHISA_OK)
393 return rc;
395 if (_shisa_rmdir3 (info->path, realm, principal) != 0)
396 return SHISA_REMOVE_PRINCIPAL_ERROR;
398 return SHISA_OK;
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,
405 void *state,
406 const char *realm, const char *principal)
408 int rc;
410 if (principal == NULL)
411 rc = realm_remove (dbh, state, realm);
412 else
413 rc = principal_remove (dbh, state, realm, principal);
415 return rc;
418 static int
419 read_key (Shisa * dbh,
420 Shisa_file * info,
421 const char *realm,
422 const char *principal, const char *keyfile, Shisa_key ** key)
424 Shisa_key tmpkey;
425 FILE *fh;
426 char *file;
427 unsigned passwdlen;
428 char junk;
429 int rc;
431 asprintf (&file, "keys/%s", keyfile);
432 fh = _shisa_fopen4 (info->path, realm, principal, file, "r");
433 free (file);
434 if (!fh)
435 return SHISA_NO_KEY;
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,
441 &tmpkey.priority);
442 if (rc != 5 && rc != 6)
443 return SHISA_NO_KEY;
445 if (rc == 5)
446 tmpkey.priority = 0;
448 if (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')
452 return SHISA_NO_KEY;
454 if (tmpkey.keylen > 0)
456 tmpkey.key = xmalloc (tmpkey.keylen + 1);
457 if (fread (tmpkey.key, 1, tmpkey.keylen, fh) != tmpkey.keylen)
458 return SHISA_NO_KEY;
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)
466 return SHISA_NO_KEY;
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)
475 return SHISA_NO_KEY;
476 tmpkey.str2keyparam[tmpkey.str2keyparamlen] = '\0';
479 if (passwdlen > 0)
481 tmpkey.password = xmalloc (passwdlen + 1);
482 if (fread (tmpkey.password, 1, passwdlen, fh) != passwdlen)
483 return SHISA_NO_KEY;
484 tmpkey.password[passwdlen] = '\0';
487 rc = fclose (fh);
488 if (rc != 0)
490 perror (keyfile);
491 return SHISA_NO_KEY;
494 *key = xmalloc (sizeof (**key));
495 memcpy (*key, &tmpkey, sizeof (tmpkey));
497 return SHISA_OK;
500 static int
501 key_match (const Shisa_key * hint, Shisa_key * key)
503 int ok = 1;
505 if (hint->kvno)
506 ok = ok && hint->kvno == key->kvno;
507 if (hint->etype)
508 ok = ok && hint->etype == key->etype;
509 if (hint->keylen)
510 ok = ok && hint->keylen == key->keylen &&
511 memcmp (hint->key, key->key, key->keylen) == 0;
512 if (hint->saltlen)
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;
519 if (hint->password)
520 ok = ok && strcmp (hint->password, key->password) == 0;
522 return ok;
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,
530 void *state,
531 const char *realm,
532 const char *principal,
533 const Shisa_key * hint,
534 Shisa_key *** keys, size_t * nkeys)
536 Shisa_file *info = state;
537 Shisa_key *tmpkey;
538 char **files;
539 size_t nfiles, matched = 0;
540 size_t i;
541 int rc;
543 files = NULL;
544 nfiles = 0;
546 rc = _shisa_ls4 (info->path, realm, principal, "keys", &files, &nfiles);
547 if (rc != SHISA_OK)
548 return SHISA_ENUMERATE_KEY_ERROR;
550 if (nkeys)
551 *nkeys = nfiles;
552 if (keys)
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))
562 if (keys)
563 (*keys)[matched] = tmpkey;
564 matched++;
566 else
567 shisa_key_free (dbh, tmpkey);
569 free (files[i]);
572 if (nfiles > 0)
573 free (files);
575 if (nkeys)
576 *nkeys = matched;
578 return rc;
581 /* Add key for PRINCIPAL@REALM. */
583 shisa_file_key_add (Shisa * dbh,
584 void *state,
585 const char *realm,
586 const char *principal, const Shisa_key * key)
588 Shisa_file *info = state;
589 size_t passwdlen = key && key->password ? strlen (key->password) : 0;
590 char *file = NULL;
591 size_t num = 0;
592 FILE *fh;
594 if (!key)
595 return SHISA_NO_KEY;
597 if (!_shisa_isdir4 (info->path, realm, principal, "keys") &&
598 _shisa_mkdir4 (info->path, realm, principal, "keys"))
599 return SHISA_NO_KEY;
603 if (file)
604 free (file);
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");
609 free (file);
610 if (!fh)
612 perror (file);
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);
618 if (key->keylen > 0)
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);
624 if (passwdlen > 0)
625 fwrite (key->password, 1, passwdlen, fh);
627 fclose (fh);
629 return SHISA_OK;
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,
637 void *state,
638 const char *realm,
639 const char *principal,
640 const Shisa_key * oldkey, const Shisa_key * newkey)
642 Shisa_file *info = state;
644 puts ("fku");
646 return SHISA_NO_KEY;
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,
654 void *state,
655 const char *realm,
656 const char *principal, const Shisa_key * key)
658 Shisa_file *info = state;
659 Shisa_key *tmpkey;
660 char **files;
661 size_t nfiles;
662 size_t i;
663 int rc;
664 char *found = NULL;
666 files = NULL;
667 nfiles = 0;
669 rc = _shisa_ls4 (info->path, realm, principal, "keys", &files, &nfiles);
670 if (rc != SHISA_OK)
671 return rc;
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))
681 if (found)
683 free (found);
684 rc = SHISA_MULTIPLE_KEY_MATCH;
686 else
687 found = xstrdup (files[i]);
689 shisa_key_free (dbh, tmpkey);
691 free (files[i]);
694 if (nfiles > 0)
695 free (files);
697 if (rc != SHISA_OK)
698 return rc;
700 if (!found)
701 return SHISA_NO_KEY;
703 rc = _shisa_rm5 (info->path, realm, principal, "keys", found);
704 free (found);
705 return rc;