1/* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements.  See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License.  You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/*
18 * Generic DAV lock implementation that a DAV provider can use.
19 */
20
21#include "apr.h"
22#include "apr_strings.h"
23#include "apr_file_io.h"
24#include "apr_uuid.h"
25
26#define APR_WANT_MEMFUNC
27#include "apr_want.h"
28
29#include "httpd.h"
30#include "http_log.h"
31
32#include "mod_dav.h"
33
34#include "locks.h"
35
36
37/* ---------------------------------------------------------------
38 *
39 * Lock database primitives
40 *
41 */
42
43/*
44 * LOCK DATABASES
45 *
46 * Lockdiscovery information is stored in the single lock database specified
47 * by the DAVGenericLockDB directive.  Information about this db is stored in
48 * the per-dir configuration.
49 *
50 * KEY
51 *
52 * The database is keyed by a key_type unsigned char (DAV_TYPE_FNAME)
53 * followed by full path.
54 *
55 * VALUE
56 *
57 * The value consists of a list of elements.
58 *    DIRECT LOCK:     [char      (DAV_LOCK_DIRECT),
59 *                      char      (dav_lock_scope),
60 *                      char      (dav_lock_type),
61 *                      int        depth,
62 *                      time_t     expires,
63 *                      apr_uuid_t locktoken,
64 *                      char[]     owner,
65 *                      char[]     auth_user]
66 *
67 *    INDIRECT LOCK:   [char      (DAV_LOCK_INDIRECT),
68 *                      apr_uuid_t locktoken,
69 *                      time_t     expires,
70 *                      int        key_size,
71 *                      char[]     key]
72 *       The key is to the collection lock that resulted in this indirect lock
73 */
74
75#define DAV_TRUE                    1
76#define DAV_FALSE                   0
77
78#define DAV_CREATE_LIST            23
79#define DAV_APPEND_LIST            24
80
81/* Stored lock_discovery prefix */
82#define DAV_LOCK_DIRECT             1
83#define DAV_LOCK_INDIRECT           2
84
85#define DAV_TYPE_FNAME             11
86
87/* Use the opaquelock scheme for locktokens */
88struct dav_locktoken {
89    apr_uuid_t uuid;
90};
91#define dav_compare_locktoken(plt1, plt2) \
92                memcmp(&(plt1)->uuid, &(plt2)->uuid, sizeof((plt1)->uuid))
93
94
95/* #################################################################
96 * ### keep these structures (internal) or move fully to dav_lock?
97 */
98
99/*
100 * We need to reliably size the fixed-length portion of
101 * dav_lock_discovery; best to separate it into another
102 * struct for a convenient sizeof, unless we pack lock_discovery.
103 */
104typedef struct dav_lock_discovery_fixed
105{
106    char scope;
107    char type;
108    int depth;
109    time_t timeout;
110} dav_lock_discovery_fixed;
111
112typedef struct dav_lock_discovery
113{
114    struct dav_lock_discovery_fixed f;
115
116    dav_locktoken *locktoken;
117    const char *owner;     /* owner field from activelock */
118    const char *auth_user; /* authenticated user who created the lock */
119    struct dav_lock_discovery *next;
120} dav_lock_discovery;
121
122/* Indirect locks represent locks inherited from containing collections.
123 * They reference the lock token for the collection the lock is
124 * inherited from. A lock provider may also define a key to the
125 * inherited lock, for fast datbase lookup. The key is opaque outside
126 * the lock provider.
127 */
128typedef struct dav_lock_indirect
129{
130    dav_locktoken *locktoken;
131    apr_datum_t key;
132    struct dav_lock_indirect *next;
133    time_t timeout;
134} dav_lock_indirect;
135
136/* ################################################################# */
137
138/*
139 * Stored direct lock info - full lock_discovery length:
140 * prefix + Fixed length + lock token + 2 strings + 2 nulls (one for each
141 * string)
142 */
143#define dav_size_direct(a)  (1 + sizeof(dav_lock_discovery_fixed) \
144                               + sizeof(apr_uuid_t) \
145                               + ((a)->owner ? strlen((a)->owner) : 0) \
146                               + ((a)->auth_user ? strlen((a)->auth_user) : 0) \
147                               + 2)
148
149/* Stored indirect lock info - lock token and apr_datum_t */
150#define dav_size_indirect(a) (1 + sizeof(apr_uuid_t) \
151                                + sizeof(time_t) \
152                                + sizeof(int) + (a)->key.dsize)
153
154/*
155 * The lockdb structure.
156 *
157 * The <db> field may be NULL, meaning one of two things:
158 * 1) That we have not actually opened the underlying database (yet). The
159 *    <opened> field should be false.
160 * 2) We opened it readonly and it wasn't present.
161 *
162 * The delayed opening (determined by <opened>) makes creating a lockdb
163 * quick, while deferring the underlying I/O until it is actually required.
164 *
165 * We export the notion of a lockdb, but hide the details of it. Most
166 * implementations will use a database of some kind, but it is certainly
167 * possible that alternatives could be used.
168 */
169struct dav_lockdb_private
170{
171    request_rec *r; /* for accessing the uuid state */
172    apr_pool_t *pool; /* a pool to use */
173    const char *lockdb_path; /* where is the lock database? */
174
175    int opened; /* we opened the database */
176    apr_dbm_t *db; /* if non-NULL, the lock database */
177};
178
179typedef struct
180{
181    dav_lockdb pub;
182    dav_lockdb_private priv;
183} dav_lockdb_combined;
184
185/*
186 * The private part of the lock structure.
187 */
188struct dav_lock_private
189{
190    apr_datum_t key;        /* key into the lock database */
191};
192typedef struct
193{
194    dav_lock pub;
195    dav_lock_private priv;
196    dav_locktoken token;
197} dav_lock_combined;
198
199/*
200 * This must be forward-declared so the open_lockdb function can use it.
201 */
202extern const dav_hooks_locks dav_hooks_locks_generic;
203
204static dav_error * dav_generic_dbm_new_error(apr_dbm_t *db, apr_pool_t *p,
205                                             apr_status_t status)
206{
207    int errcode;
208    const char *errstr;
209    dav_error *err;
210    char errbuf[200];
211
212    if (status == APR_SUCCESS) {
213        return NULL;
214    }
215
216    /* There might not be a <db> if we had problems creating it. */
217    if (db == NULL) {
218        errcode = 1;
219        errstr = "Could not open property database.";
220    }
221    else {
222        (void) apr_dbm_geterror(db, &errcode, errbuf, sizeof(errbuf));
223        errstr = apr_pstrdup(p, errbuf);
224    }
225
226    err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, errcode, status, errstr);
227    return err;
228}
229
230/* internal function for creating locks */
231static dav_lock *dav_generic_alloc_lock(dav_lockdb *lockdb, apr_datum_t key,
232                                        const dav_locktoken *locktoken)
233{
234    dav_lock_combined *comb;
235
236    comb = apr_pcalloc(lockdb->info->pool, sizeof(*comb));
237    comb->pub.rectype = DAV_LOCKREC_DIRECT;
238    comb->pub.info = &comb->priv;
239    comb->priv.key = key;
240
241    if (locktoken == NULL) {
242        comb->pub.locktoken = &comb->token;
243        apr_uuid_get(&comb->token.uuid);
244    }
245    else {
246        comb->pub.locktoken = locktoken;
247    }
248
249    return &comb->pub;
250}
251
252/*
253 * dav_generic_parse_locktoken
254 *
255 * Parse an opaquelocktoken URI into a locktoken.
256 */
257static dav_error * dav_generic_parse_locktoken(apr_pool_t *p,
258                                               const char *char_token,
259                                               dav_locktoken **locktoken_p)
260{
261    dav_locktoken *locktoken;
262
263    if (ap_strstr_c(char_token, "opaquelocktoken:") != char_token) {
264        return dav_new_error(p,
265                             HTTP_BAD_REQUEST, DAV_ERR_LOCK_UNK_STATE_TOKEN, 0,
266                             "The lock token uses an unknown State-token "
267                             "format and could not be parsed.");
268    }
269    char_token += 16;
270
271    locktoken = apr_pcalloc(p, sizeof(*locktoken));
272    if (apr_uuid_parse(&locktoken->uuid, char_token)) {
273        return dav_new_error(p, HTTP_BAD_REQUEST, DAV_ERR_LOCK_PARSE_TOKEN, 0,
274                             "The opaquelocktoken has an incorrect format "
275                             "and could not be parsed.");
276    }
277
278    *locktoken_p = locktoken;
279    return NULL;
280}
281
282/*
283 * dav_generic_format_locktoken
284 *
285 * Generate the URI for a locktoken
286 */
287static const char *dav_generic_format_locktoken(apr_pool_t *p,
288                                                const dav_locktoken *locktoken)
289{
290    char buf[APR_UUID_FORMATTED_LENGTH + 1];
291
292    apr_uuid_format(buf, &locktoken->uuid);
293    return apr_pstrcat(p, "opaquelocktoken:", buf, NULL);
294}
295
296/*
297 * dav_generic_compare_locktoken
298 *
299 * Determine whether two locktokens are the same
300 */
301static int dav_generic_compare_locktoken(const dav_locktoken *lt1,
302                                         const dav_locktoken *lt2)
303{
304    return dav_compare_locktoken(lt1, lt2);
305}
306
307/*
308 * dav_generic_really_open_lockdb:
309 *
310 * If the database hasn't been opened yet, then open the thing.
311 */
312static dav_error * dav_generic_really_open_lockdb(dav_lockdb *lockdb)
313{
314    dav_error *err;
315    apr_status_t status;
316
317    if (lockdb->info->opened) {
318        return NULL;
319    }
320
321    status = apr_dbm_open(&lockdb->info->db, lockdb->info->lockdb_path,
322                          lockdb->ro ? APR_DBM_READONLY : APR_DBM_RWCREATE,
323                          APR_OS_DEFAULT, lockdb->info->pool);
324
325    if (status) {
326        err = dav_generic_dbm_new_error(lockdb->info->db, lockdb->info->pool,
327                                        status);
328        return dav_push_error(lockdb->info->pool,
329                              HTTP_INTERNAL_SERVER_ERROR,
330                              DAV_ERR_LOCK_OPENDB,
331                              "Could not open the lock database.",
332                              err);
333    }
334
335    /* all right. it is opened now. */
336    lockdb->info->opened = 1;
337
338    return NULL;
339}
340
341/*
342 * dav_generic_open_lockdb:
343 *
344 * "open" the lock database, as specified in the global server configuration.
345 * If force is TRUE, then the database is opened now, rather than lazily.
346 *
347 * Note that only one can be open read/write.
348 */
349static dav_error * dav_generic_open_lockdb(request_rec *r, int ro, int force,
350                                           dav_lockdb **lockdb)
351{
352    dav_lockdb_combined *comb;
353
354    comb = apr_pcalloc(r->pool, sizeof(*comb));
355    comb->pub.hooks = &dav_hooks_locks_generic;
356    comb->pub.ro = ro;
357    comb->pub.info = &comb->priv;
358    comb->priv.r = r;
359    comb->priv.pool = r->pool;
360
361    comb->priv.lockdb_path = dav_generic_get_lockdb_path(r);
362    if (comb->priv.lockdb_path == NULL) {
363        return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR,
364                             DAV_ERR_LOCK_NO_DB, 0,
365                             "A lock database was not specified with the "
366                             "DAVGenericLockDB directive. One must be "
367                             "specified to use the locking functionality.");
368    }
369
370    /* done initializing. return it. */
371    *lockdb = &comb->pub;
372
373    if (force) {
374        /* ### add a higher-level comment? */
375        return dav_generic_really_open_lockdb(*lockdb);
376    }
377
378    return NULL;
379}
380
381/*
382 * dav_generic_close_lockdb:
383 *
384 * Close it. Duh.
385 */
386static void dav_generic_close_lockdb(dav_lockdb *lockdb)
387{
388    if (lockdb->info->db != NULL) {
389        apr_dbm_close(lockdb->info->db);
390    }
391    lockdb->info->opened = 0;
392}
393
394/*
395 * dav_generic_build_key
396 *
397 * Given a pathname, build a DAV_TYPE_FNAME lock database key.
398 */
399static apr_datum_t dav_generic_build_key(apr_pool_t *p,
400                                         const dav_resource *resource)
401{
402    apr_datum_t key;
403    const char *pathname = resource->uri;
404
405    /* ### does this allocation have a proper lifetime? need to check */
406    /* ### can we use a buffer for this? */
407
408    /* size is TYPE + pathname + null */
409    key.dsize = strlen(pathname) + 2;
410    key.dptr = apr_palloc(p, key.dsize);
411    *key.dptr = DAV_TYPE_FNAME;
412    memcpy(key.dptr + 1, pathname, key.dsize - 1);
413    if (key.dptr[key.dsize - 2] == '/')
414        key.dptr[--key.dsize - 1] = '\0';
415    return key;
416}
417
418/*
419 * dav_generic_lock_expired:  return 1 (true) if the given timeout is in the
420 *    past or present (the lock has expired), or 0 (false) if in the future
421 *    (the lock has not yet expired).
422 */
423static int dav_generic_lock_expired(time_t expires)
424{
425    return expires != DAV_TIMEOUT_INFINITE && time(NULL) >= expires;
426}
427
428/*
429 * dav_generic_save_lock_record:  Saves the lock information specified in the
430 *    direct and indirect lock lists about path into the lock database.
431 *    If direct and indirect == NULL, the key is removed.
432 */
433static dav_error * dav_generic_save_lock_record(dav_lockdb *lockdb,
434                                                apr_datum_t key,
435                                                dav_lock_discovery *direct,
436                                                dav_lock_indirect *indirect)
437{
438    dav_error *err;
439    apr_status_t status;
440    apr_datum_t val = { 0 };
441    char *ptr;
442    dav_lock_discovery *dp = direct;
443    dav_lock_indirect *ip = indirect;
444
445#if DAV_DEBUG
446    if (lockdb->ro) {
447        return dav_new_error(lockdb->info->pool,
448                             HTTP_INTERNAL_SERVER_ERROR, 0, 0,
449                             "INTERNAL DESIGN ERROR: the lockdb was opened "
450                             "readonly, but an attempt to save locks was "
451                             "performed.");
452    }
453#endif
454
455    if ((err = dav_generic_really_open_lockdb(lockdb)) != NULL) {
456        /* ### add a higher-level error? */
457        return err;
458    }
459
460    /* If nothing to save, delete key */
461    if (dp == NULL && ip == NULL) {
462        /* don't fail if the key is not present */
463        /* ### but what about other errors? */
464        apr_dbm_delete(lockdb->info->db, key);
465        return NULL;
466    }
467
468    while(dp) {
469        val.dsize += dav_size_direct(dp);
470        dp = dp->next;
471    }
472    while(ip) {
473        val.dsize += dav_size_indirect(ip);
474        ip = ip->next;
475    }
476
477    /* ### can this be apr_palloc() ? */
478    /* ### hmmm.... investigate the use of a buffer here */
479    ptr = val.dptr = apr_pcalloc(lockdb->info->pool, val.dsize);
480    dp  = direct;
481    ip  = indirect;
482
483    while(dp) {
484        /* Direct lock - lock_discovery struct follows */
485        *ptr++ = DAV_LOCK_DIRECT;
486        memcpy(ptr, dp, sizeof(dp->f));        /* Fixed portion of struct */
487        ptr += sizeof(dp->f);
488        memcpy(ptr, dp->locktoken, sizeof(*dp->locktoken));
489        ptr += sizeof(*dp->locktoken);
490        if (dp->owner == NULL) {
491            *ptr++ = '\0';
492        }
493        else {
494            memcpy(ptr, dp->owner, strlen(dp->owner) + 1);
495            ptr += strlen(dp->owner) + 1;
496        }
497        if (dp->auth_user == NULL) {
498            *ptr++ = '\0';
499        }
500        else {
501            memcpy(ptr, dp->auth_user, strlen(dp->auth_user) + 1);
502            ptr += strlen(dp->auth_user) + 1;
503        }
504
505        dp = dp->next;
506    }
507
508    while(ip) {
509        /* Indirect lock prefix */
510        *ptr++ = DAV_LOCK_INDIRECT;
511
512        memcpy(ptr, ip->locktoken, sizeof(*ip->locktoken));
513        ptr += sizeof(*ip->locktoken);
514
515        memcpy(ptr, &ip->timeout, sizeof(ip->timeout));
516        ptr += sizeof(ip->timeout);
517
518        memcpy(ptr, &ip->key.dsize, sizeof(ip->key.dsize));
519        ptr += sizeof(ip->key.dsize);
520
521        memcpy(ptr, ip->key.dptr, ip->key.dsize);
522        ptr += ip->key.dsize;
523
524        ip = ip->next;
525    }
526
527    if ((status = apr_dbm_store(lockdb->info->db, key, val)) != APR_SUCCESS) {
528        /* ### more details? add an error_id? */
529        err = dav_generic_dbm_new_error(lockdb->info->db, lockdb->info->pool,
530                                        status);
531        return dav_push_error(lockdb->info->pool,
532                              HTTP_INTERNAL_SERVER_ERROR,
533                              DAV_ERR_LOCK_SAVE_LOCK,
534                              "Could not save lock information.",
535                              err);
536    }
537
538    return NULL;
539}
540
541/*
542 * dav_load_lock_record:  Reads lock information about key from lock db;
543 *    creates linked lists of the direct and indirect locks.
544 *
545 *    If add_method = DAV_APPEND_LIST, the result will be appended to the
546 *    head of the direct and indirect lists supplied.
547 *
548 *    Passive lock removal:  If lock has timed out, it will not be returned.
549 *    ### How much "logging" does RFC 2518 require?
550 */
551static dav_error * dav_generic_load_lock_record(dav_lockdb *lockdb,
552                                                apr_datum_t key,
553                                                int add_method,
554                                                dav_lock_discovery **direct,
555                                                dav_lock_indirect **indirect)
556{
557    apr_pool_t *p = lockdb->info->pool;
558    dav_error *err;
559    apr_status_t status;
560    apr_size_t offset = 0;
561    int need_save = DAV_FALSE;
562    apr_datum_t val = { 0 };
563    dav_lock_discovery *dp;
564    dav_lock_indirect *ip;
565
566    if (add_method != DAV_APPEND_LIST) {
567        *direct = NULL;
568        *indirect = NULL;
569    }
570
571    if ((err = dav_generic_really_open_lockdb(lockdb)) != NULL) {
572        /* ### add a higher-level error? */
573        return err;
574    }
575
576    /*
577     * If we opened readonly and the db wasn't there, then there are no
578     * locks for this resource. Just exit.
579     */
580    if (lockdb->info->db == NULL) {
581        return NULL;
582    }
583
584    if ((status = apr_dbm_fetch(lockdb->info->db, key, &val)) != APR_SUCCESS) {
585        return dav_generic_dbm_new_error(lockdb->info->db, p, status);
586    }
587
588    if (!val.dsize) {
589        return NULL;
590    }
591
592    while (offset < val.dsize) {
593        switch (*(val.dptr + offset++)) {
594        case DAV_LOCK_DIRECT:
595            /* Create and fill a dav_lock_discovery structure */
596
597            dp = apr_pcalloc(p, sizeof(*dp));
598
599            /* Copy the dav_lock_discovery_fixed portion */
600            memcpy(dp, val.dptr + offset, sizeof(dp->f));
601            offset += sizeof(dp->f);
602
603            /* Copy the lock token. */
604            dp->locktoken = apr_pmemdup(p, val.dptr + offset, sizeof(*dp->locktoken));
605            offset += sizeof(*dp->locktoken);
606
607            /* Do we have an owner field? */
608            if (*(val.dptr + offset) == '\0') {
609                ++offset;
610            }
611            else {
612                apr_size_t len = strlen(val.dptr + offset);
613                dp->owner = apr_pstrmemdup(p, val.dptr + offset, len);
614                offset += len + 1;
615            }
616
617            if (*(val.dptr + offset) == '\0') {
618                ++offset;
619            }
620            else {
621                apr_size_t len = strlen(val.dptr + offset);
622                dp->auth_user = apr_pstrmemdup(p, val.dptr + offset, len);
623                offset += len + 1;
624            }
625
626            if (!dav_generic_lock_expired(dp->f.timeout)) {
627                dp->next = *direct;
628                *direct = dp;
629            }
630            else {
631                need_save = DAV_TRUE;
632            }
633            break;
634
635        case DAV_LOCK_INDIRECT:
636            /* Create and fill a dav_lock_indirect structure */
637
638            ip = apr_pcalloc(p, sizeof(*ip));
639            ip->locktoken = apr_pmemdup(p, val.dptr + offset, sizeof(*ip->locktoken));
640            offset += sizeof(*ip->locktoken);
641            memcpy(&ip->timeout, val.dptr + offset, sizeof(ip->timeout));
642            offset += sizeof(ip->timeout);
643            /* length of datum */
644            ip->key.dsize = *((int *) (val.dptr + offset));
645            offset += sizeof(ip->key.dsize);
646            ip->key.dptr = apr_pmemdup(p, val.dptr + offset, ip->key.dsize);
647            offset += ip->key.dsize;
648
649            if (!dav_generic_lock_expired(ip->timeout)) {
650                ip->next = *indirect;
651                *indirect = ip;
652            }
653            else {
654                need_save = DAV_TRUE;
655            }
656
657            break;
658
659        default:
660            apr_dbm_freedatum(lockdb->info->db, val);
661
662            /* ### should use a computed_desc and insert corrupt token data */
663            --offset;
664            return dav_new_error(p,
665                                 HTTP_INTERNAL_SERVER_ERROR,
666                                 DAV_ERR_LOCK_CORRUPT_DB, 0,
667                                 apr_psprintf(p,
668                                             "The lock database was found to "
669                                             "be corrupt. offset %"
670                                             APR_SIZE_T_FMT ", c=%02x",
671                                             offset, val.dptr[offset]));
672        }
673    }
674
675    apr_dbm_freedatum(lockdb->info->db, val);
676
677    /* Clean up this record if we found expired locks */
678    /*
679     * ### shouldn't do this if we've been opened READONLY. elide the
680     * ### timed-out locks from the response, but don't save that info back
681     */
682    if (need_save == DAV_TRUE) {
683        return dav_generic_save_lock_record(lockdb, key, *direct, *indirect);
684    }
685
686    return NULL;
687}
688
689/* resolve <indirect>, returning <*direct> */
690static dav_error * dav_generic_resolve(dav_lockdb *lockdb,
691                                       dav_lock_indirect *indirect,
692                                       dav_lock_discovery **direct,
693                                       dav_lock_discovery **ref_dp,
694                                       dav_lock_indirect **ref_ip)
695{
696    dav_error *err;
697    dav_lock_discovery *dir;
698    dav_lock_indirect *ind;
699
700    if ((err = dav_generic_load_lock_record(lockdb, indirect->key,
701                                       DAV_CREATE_LIST,
702                                       &dir, &ind)) != NULL) {
703        /* ### insert a higher-level description? */
704        return err;
705    }
706    if (ref_dp != NULL) {
707        *ref_dp = dir;
708        *ref_ip = ind;
709    }
710
711    for (; dir != NULL; dir = dir->next) {
712        if (!dav_compare_locktoken(indirect->locktoken, dir->locktoken)) {
713            *direct = dir;
714            return NULL;
715        }
716    }
717
718    /* No match found (but we should have found one!) */
719
720    /* ### use a different description and/or error ID? */
721    return dav_new_error(lockdb->info->pool,
722                         HTTP_INTERNAL_SERVER_ERROR,
723                         DAV_ERR_LOCK_CORRUPT_DB, 0,
724                         "The lock database was found to be corrupt. "
725                         "An indirect lock's direct lock could not "
726                         "be found.");
727}
728
729/* ---------------------------------------------------------------
730 *
731 * Property-related lock functions
732 *
733 */
734
735/*
736 * dav_generic_get_supportedlock:  Returns a static string for all
737 *    supportedlock properties. I think we save more returning a static string
738 *    than constructing it every time, though it might look cleaner.
739 */
740static const char *dav_generic_get_supportedlock(const dav_resource *resource)
741{
742    static const char supported[] = DEBUG_CR
743        "<D:lockentry>" DEBUG_CR
744        "<D:lockscope><D:exclusive/></D:lockscope>" DEBUG_CR
745        "<D:locktype><D:write/></D:locktype>" DEBUG_CR
746        "</D:lockentry>" DEBUG_CR
747        "<D:lockentry>" DEBUG_CR
748        "<D:lockscope><D:shared/></D:lockscope>" DEBUG_CR
749        "<D:locktype><D:write/></D:locktype>" DEBUG_CR
750        "</D:lockentry>" DEBUG_CR;
751
752    return supported;
753}
754
755/* ---------------------------------------------------------------
756 *
757 * General lock functions
758 *
759 */
760
761static dav_error * dav_generic_remove_locknull_state(dav_lockdb *lockdb,
762                                                 const dav_resource *resource)
763{
764    /* We don't need to do anything. */
765    return NULL;
766}
767
768static dav_error * dav_generic_create_lock(dav_lockdb *lockdb,
769                                      const dav_resource *resource,
770                                      dav_lock **lock)
771{
772    apr_datum_t key;
773
774    key = dav_generic_build_key(lockdb->info->pool, resource);
775
776    *lock = dav_generic_alloc_lock(lockdb, key, NULL);
777
778    (*lock)->is_locknull = !resource->exists;
779
780    return NULL;
781}
782
783static dav_error * dav_generic_get_locks(dav_lockdb *lockdb,
784                                         const dav_resource *resource,
785                                         int calltype,
786                                         dav_lock **locks)
787{
788    apr_pool_t *p = lockdb->info->pool;
789    apr_datum_t key;
790    dav_error *err;
791    dav_lock *lock = NULL;
792    dav_lock *newlock;
793    dav_lock_discovery *dp;
794    dav_lock_indirect *ip;
795
796#if DAV_DEBUG
797    if (calltype == DAV_GETLOCKS_COMPLETE) {
798        return dav_new_error(lockdb->info->pool,
799                             HTTP_INTERNAL_SERVER_ERROR, 0, 0,
800                             "INTERNAL DESIGN ERROR: DAV_GETLOCKS_COMPLETE "
801                             "is not yet supported");
802    }
803#endif
804
805    key = dav_generic_build_key(p, resource);
806    if ((err = dav_generic_load_lock_record(lockdb, key, DAV_CREATE_LIST,
807                                            &dp, &ip)) != NULL) {
808        /* ### push a higher-level desc? */
809        return err;
810    }
811
812    /* copy all direct locks to the result list */
813    for (; dp != NULL; dp = dp->next) {
814        newlock = dav_generic_alloc_lock(lockdb, key, dp->locktoken);
815        newlock->is_locknull = !resource->exists;
816        newlock->scope = dp->f.scope;
817        newlock->type = dp->f.type;
818        newlock->depth = dp->f.depth;
819        newlock->timeout = dp->f.timeout;
820        newlock->owner = dp->owner;
821        newlock->auth_user = dp->auth_user;
822
823        /* hook into the result list */
824        newlock->next = lock;
825        lock = newlock;
826    }
827
828    /* copy all the indirect locks to the result list. resolve as needed. */
829    for (; ip != NULL; ip = ip->next) {
830        newlock = dav_generic_alloc_lock(lockdb, ip->key, ip->locktoken);
831        newlock->is_locknull = !resource->exists;
832
833        if (calltype == DAV_GETLOCKS_RESOLVED) {
834            err = dav_generic_resolve(lockdb, ip, &dp, NULL, NULL);
835            if (err != NULL) {
836                /* ### push a higher-level desc? */
837                return err;
838            }
839
840            newlock->scope = dp->f.scope;
841            newlock->type = dp->f.type;
842            newlock->depth = dp->f.depth;
843            newlock->timeout = dp->f.timeout;
844            newlock->owner = dp->owner;
845            newlock->auth_user = dp->auth_user;
846        }
847        else {
848            /* DAV_GETLOCKS_PARTIAL */
849            newlock->rectype = DAV_LOCKREC_INDIRECT_PARTIAL;
850        }
851
852        /* hook into the result list */
853        newlock->next = lock;
854        lock = newlock;
855    }
856
857    *locks = lock;
858    return NULL;
859}
860
861static dav_error * dav_generic_find_lock(dav_lockdb *lockdb,
862                                         const dav_resource *resource,
863                                         const dav_locktoken *locktoken,
864                                         int partial_ok,
865                                         dav_lock **lock)
866{
867    dav_error *err;
868    apr_datum_t key;
869    dav_lock_discovery *dp;
870    dav_lock_indirect *ip;
871
872    *lock = NULL;
873
874    key = dav_generic_build_key(lockdb->info->pool, resource);
875    if ((err = dav_generic_load_lock_record(lockdb, key, DAV_CREATE_LIST,
876                                       &dp, &ip)) != NULL) {
877        /* ### push a higher-level desc? */
878        return err;
879    }
880
881    for (; dp != NULL; dp = dp->next) {
882        if (!dav_compare_locktoken(locktoken, dp->locktoken)) {
883            *lock = dav_generic_alloc_lock(lockdb, key, locktoken);
884            (*lock)->is_locknull = !resource->exists;
885            (*lock)->scope = dp->f.scope;
886            (*lock)->type = dp->f.type;
887            (*lock)->depth = dp->f.depth;
888            (*lock)->timeout = dp->f.timeout;
889            (*lock)->owner = dp->owner;
890            (*lock)->auth_user = dp->auth_user;
891            return NULL;
892        }
893    }
894
895    for (; ip != NULL; ip = ip->next) {
896        if (!dav_compare_locktoken(locktoken, ip->locktoken)) {
897            *lock = dav_generic_alloc_lock(lockdb, ip->key, locktoken);
898            (*lock)->is_locknull = !resource->exists;
899
900            /* ### nobody uses the resolving right now! */
901            if (partial_ok) {
902                (*lock)->rectype = DAV_LOCKREC_INDIRECT_PARTIAL;
903            }
904            else {
905                (*lock)->rectype = DAV_LOCKREC_INDIRECT;
906                if ((err = dav_generic_resolve(lockdb, ip, &dp,
907                                          NULL, NULL)) != NULL) {
908                    /* ### push a higher-level desc? */
909                    return err;
910                }
911                (*lock)->scope = dp->f.scope;
912                (*lock)->type = dp->f.type;
913                (*lock)->depth = dp->f.depth;
914                (*lock)->timeout = dp->f.timeout;
915                (*lock)->owner = dp->owner;
916                (*lock)->auth_user = dp->auth_user;
917            }
918            return NULL;
919        }
920    }
921
922    return NULL;
923}
924
925static dav_error * dav_generic_has_locks(dav_lockdb *lockdb,
926                                         const dav_resource *resource,
927                                         int *locks_present)
928{
929    dav_error *err;
930    apr_datum_t key;
931
932    *locks_present = 0;
933
934    if ((err = dav_generic_really_open_lockdb(lockdb)) != NULL) {
935        /* ### insert a higher-level error description */
936        return err;
937    }
938
939    /*
940     * If we opened readonly and the db wasn't there, then there are no
941     * locks for this resource. Just exit.
942     */
943    if (lockdb->info->db == NULL)
944        return NULL;
945
946    key = dav_generic_build_key(lockdb->info->pool, resource);
947
948    *locks_present = apr_dbm_exists(lockdb->info->db, key);
949
950    return NULL;
951}
952
953static dav_error * dav_generic_append_locks(dav_lockdb *lockdb,
954                                            const dav_resource *resource,
955                                            int make_indirect,
956                                            const dav_lock *lock)
957{
958    apr_pool_t *p = lockdb->info->pool;
959    dav_error *err;
960    dav_lock_indirect *ip;
961    dav_lock_discovery *dp;
962    apr_datum_t key;
963
964    key = dav_generic_build_key(lockdb->info->pool, resource);
965
966    err = dav_generic_load_lock_record(lockdb, key, 0, &dp, &ip);
967    if (err != NULL) {
968        /* ### maybe add in a higher-level description */
969        return err;
970    }
971
972    /*
973     * ### when we store the lock more directly, we need to update
974     * ### lock->rectype and lock->is_locknull
975     */
976
977    if (make_indirect) {
978        for (; lock != NULL; lock = lock->next) {
979
980            /* ### this works for any <lock> rectype */
981            dav_lock_indirect *newi = apr_pcalloc(p, sizeof(*newi));
982
983            /* ### shut off the const warning for now */
984            newi->locktoken = (dav_locktoken *)lock->locktoken;
985            newi->timeout   = lock->timeout;
986            newi->key       = lock->info->key;
987            newi->next      = ip;
988            ip              = newi;
989        }
990    }
991    else {
992        for (; lock != NULL; lock = lock->next) {
993            /* create and link in the right kind of lock */
994
995            if (lock->rectype == DAV_LOCKREC_DIRECT) {
996                dav_lock_discovery *newd = apr_pcalloc(p, sizeof(*newd));
997
998                newd->f.scope = lock->scope;
999                newd->f.type = lock->type;
1000                newd->f.depth = lock->depth;
1001                newd->f.timeout = lock->timeout;
1002                /* ### shut off the const warning for now */
1003                newd->locktoken = (dav_locktoken *)lock->locktoken;
1004                newd->owner = lock->owner;
1005                newd->auth_user = lock->auth_user;
1006                newd->next = dp;
1007                dp = newd;
1008            }
1009            else {
1010                /* DAV_LOCKREC_INDIRECT(_PARTIAL) */
1011
1012                dav_lock_indirect *newi = apr_pcalloc(p, sizeof(*newi));
1013
1014                /* ### shut off the const warning for now */
1015                newi->locktoken = (dav_locktoken *)lock->locktoken;
1016                newi->key       = lock->info->key;
1017                newi->next      = ip;
1018                ip              = newi;
1019            }
1020        }
1021    }
1022
1023    if ((err = dav_generic_save_lock_record(lockdb, key, dp, ip)) != NULL) {
1024        /* ### maybe add a higher-level description */
1025        return err;
1026    }
1027
1028    return NULL;
1029}
1030
1031static dav_error * dav_generic_remove_lock(dav_lockdb *lockdb,
1032                                           const dav_resource *resource,
1033                                           const dav_locktoken *locktoken)
1034{
1035    dav_error *err;
1036    dav_lock_discovery *dh = NULL;
1037    dav_lock_indirect *ih = NULL;
1038    apr_datum_t key;
1039
1040    key = dav_generic_build_key(lockdb->info->pool, resource);
1041
1042    if (locktoken != NULL) {
1043        dav_lock_discovery *dp;
1044        dav_lock_discovery *dprev = NULL;
1045        dav_lock_indirect *ip;
1046        dav_lock_indirect *iprev = NULL;
1047
1048        if ((err = dav_generic_load_lock_record(lockdb, key, DAV_CREATE_LIST,
1049                                           &dh, &ih)) != NULL) {
1050            /* ### maybe add a higher-level description */
1051            return err;
1052        }
1053
1054        for (dp = dh; dp != NULL; dp = dp->next) {
1055            if (dav_compare_locktoken(locktoken, dp->locktoken) == 0) {
1056                if (dprev)
1057                    dprev->next = dp->next;
1058                else
1059                    dh = dh->next;
1060            }
1061            dprev = dp;
1062        }
1063
1064        for (ip = ih; ip != NULL; ip = ip->next) {
1065            if (dav_compare_locktoken(locktoken, ip->locktoken) == 0) {
1066                if (iprev)
1067                    iprev->next = ip->next;
1068                else
1069                    ih = ih->next;
1070            }
1071            iprev = ip;
1072        }
1073
1074    }
1075
1076    /* save the modified locks, or remove all locks (dh=ih=NULL). */
1077    if ((err = dav_generic_save_lock_record(lockdb, key, dh, ih)) != NULL) {
1078        /* ### maybe add a higher-level description */
1079        return err;
1080    }
1081
1082    return NULL;
1083}
1084
1085static int dav_generic_do_refresh(dav_lock_discovery *dp,
1086                                  const dav_locktoken_list *ltl,
1087                                  time_t new_time)
1088{
1089    int dirty = 0;
1090
1091    for (; ltl != NULL; ltl = ltl->next) {
1092        if (dav_compare_locktoken(dp->locktoken, ltl->locktoken) == 0)
1093        {
1094            dp->f.timeout = new_time;
1095            dirty = 1;
1096            break;
1097        }
1098    }
1099
1100    return dirty;
1101}
1102
1103static dav_error * dav_generic_refresh_locks(dav_lockdb *lockdb,
1104                                             const dav_resource *resource,
1105                                             const dav_locktoken_list *ltl,
1106                                             time_t new_time,
1107                                             dav_lock **locks)
1108{
1109    dav_error *err;
1110    apr_datum_t key;
1111    dav_lock_discovery *dp;
1112    dav_lock_discovery *dp_scan;
1113    dav_lock_indirect *ip;
1114    int dirty = 0;
1115    dav_lock *newlock;
1116
1117    *locks = NULL;
1118
1119    key = dav_generic_build_key(lockdb->info->pool, resource);
1120    if ((err = dav_generic_load_lock_record(lockdb, key, DAV_CREATE_LIST,
1121                                            &dp, &ip)) != NULL) {
1122        /* ### maybe add in a higher-level description */
1123        return err;
1124    }
1125
1126    /* ### we should be refreshing direct AND (resolved) indirect locks! */
1127
1128    /* refresh all of the direct locks on this resource */
1129    for (dp_scan = dp; dp_scan != NULL; dp_scan = dp_scan->next) {
1130        if (dav_generic_do_refresh(dp_scan, ltl, new_time)) {
1131            /* the lock was refreshed. return the lock. */
1132            newlock = dav_generic_alloc_lock(lockdb, key, dp_scan->locktoken);
1133            newlock->is_locknull = !resource->exists;
1134            newlock->scope = dp_scan->f.scope;
1135            newlock->type = dp_scan->f.type;
1136            newlock->depth = dp_scan->f.depth;
1137            newlock->timeout = dp_scan->f.timeout;
1138            newlock->owner = dp_scan->owner;
1139            newlock->auth_user = dp_scan->auth_user;
1140
1141            newlock->next = *locks;
1142            *locks = newlock;
1143
1144            dirty = 1;
1145        }
1146    }
1147
1148    /* if we refreshed any locks, then save them back. */
1149    if (dirty
1150        && (err = dav_generic_save_lock_record(lockdb, key, dp, ip)) != NULL) {
1151        /* ### maybe add in a higher-level description */
1152        return err;
1153    }
1154
1155    /* for each indirect lock, find its direct lock and refresh it. */
1156    for (; ip != NULL; ip = ip->next) {
1157        dav_lock_discovery *ref_dp;
1158        dav_lock_indirect *ref_ip;
1159
1160        if ((err = dav_generic_resolve(lockdb, ip, &dp_scan,
1161                                       &ref_dp, &ref_ip)) != NULL) {
1162            /* ### push a higher-level desc? */
1163            return err;
1164        }
1165        if (dav_generic_do_refresh(dp_scan, ltl, new_time)) {
1166            /* the lock was refreshed. return the lock. */
1167            newlock = dav_generic_alloc_lock(lockdb, ip->key, dp->locktoken);
1168            newlock->is_locknull = !resource->exists;
1169            newlock->scope = dp->f.scope;
1170            newlock->type = dp->f.type;
1171            newlock->depth = dp->f.depth;
1172            newlock->timeout = dp->f.timeout;
1173            newlock->owner = dp->owner;
1174            newlock->auth_user = dp_scan->auth_user;
1175
1176            newlock->next = *locks;
1177            *locks = newlock;
1178
1179            /* save the (resolved) direct lock back */
1180            if ((err = dav_generic_save_lock_record(lockdb, ip->key, ref_dp,
1181                                                    ref_ip)) != NULL) {
1182                /* ### push a higher-level desc? */
1183                return err;
1184            }
1185        }
1186    }
1187
1188    return NULL;
1189}
1190
1191
1192const dav_hooks_locks dav_hooks_locks_generic =
1193{
1194    dav_generic_get_supportedlock,
1195    dav_generic_parse_locktoken,
1196    dav_generic_format_locktoken,
1197    dav_generic_compare_locktoken,
1198    dav_generic_open_lockdb,
1199    dav_generic_close_lockdb,
1200    dav_generic_remove_locknull_state,
1201    dav_generic_create_lock,
1202    dav_generic_get_locks,
1203    dav_generic_find_lock,
1204    dav_generic_has_locks,
1205    dav_generic_append_locks,
1206    dav_generic_remove_lock,
1207    dav_generic_refresh_locks,
1208    NULL, /* lookup_resource */
1209
1210    NULL /* ctx */
1211};
1212