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 save_errno = errno;
208    int errcode;
209    const char *errstr;
210    dav_error *err;
211    char errbuf[200];
212
213    if (status == APR_SUCCESS) {
214        return NULL;
215    }
216
217    /* There might not be a <db> if we had problems creating it. */
218    if (db == NULL) {
219        errcode = 1;
220        errstr = "Could not open property database.";
221    }
222    else {
223        (void) apr_dbm_geterror(db, &errcode, errbuf, sizeof(errbuf));
224        errstr = apr_pstrdup(p, errbuf);
225    }
226
227    err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, errcode, errstr);
228    err->save_errno = save_errno;
229    return err;
230}
231
232/* internal function for creating locks */
233static dav_lock *dav_generic_alloc_lock(dav_lockdb *lockdb, apr_datum_t key,
234                                        const dav_locktoken *locktoken)
235{
236    dav_lock_combined *comb;
237
238    comb = apr_pcalloc(lockdb->info->pool, sizeof(*comb));
239    comb->pub.rectype = DAV_LOCKREC_DIRECT;
240    comb->pub.info = &comb->priv;
241    comb->priv.key = key;
242
243    if (locktoken == NULL) {
244        comb->pub.locktoken = &comb->token;
245        apr_uuid_get(&comb->token.uuid);
246    }
247    else {
248        comb->pub.locktoken = locktoken;
249    }
250
251    return &comb->pub;
252}
253
254/*
255 * dav_generic_parse_locktoken
256 *
257 * Parse an opaquelocktoken URI into a locktoken.
258 */
259static dav_error * dav_generic_parse_locktoken(apr_pool_t *p,
260                                               const char *char_token,
261                                               dav_locktoken **locktoken_p)
262{
263    dav_locktoken *locktoken;
264
265    if (ap_strstr_c(char_token, "opaquelocktoken:") != char_token) {
266        return dav_new_error(p,
267                             HTTP_BAD_REQUEST, DAV_ERR_LOCK_UNK_STATE_TOKEN,
268                             "The lock token uses an unknown State-token "
269                             "format and could not be parsed.");
270    }
271    char_token += 16;
272
273    locktoken = apr_pcalloc(p, sizeof(*locktoken));
274    if (apr_uuid_parse(&locktoken->uuid, char_token)) {
275        return dav_new_error(p, HTTP_BAD_REQUEST, DAV_ERR_LOCK_PARSE_TOKEN,
276                             "The opaquelocktoken has an incorrect format "
277                             "and could not be parsed.");
278    }
279
280    *locktoken_p = locktoken;
281    return NULL;
282}
283
284/*
285 * dav_generic_format_locktoken
286 *
287 * Generate the URI for a locktoken
288 */
289static const char *dav_generic_format_locktoken(apr_pool_t *p,
290                                                const dav_locktoken *locktoken)
291{
292    char buf[APR_UUID_FORMATTED_LENGTH + 1];
293
294    apr_uuid_format(buf, &locktoken->uuid);
295    return apr_pstrcat(p, "opaquelocktoken:", buf, NULL);
296}
297
298/*
299 * dav_generic_compare_locktoken
300 *
301 * Determine whether two locktokens are the same
302 */
303static int dav_generic_compare_locktoken(const dav_locktoken *lt1,
304                                         const dav_locktoken *lt2)
305{
306    return dav_compare_locktoken(lt1, lt2);
307}
308
309/*
310 * dav_generic_really_open_lockdb:
311 *
312 * If the database hasn't been opened yet, then open the thing.
313 */
314static dav_error * dav_generic_really_open_lockdb(dav_lockdb *lockdb)
315{
316    dav_error *err;
317    apr_status_t status;
318
319    if (lockdb->info->opened) {
320        return NULL;
321    }
322
323    status = apr_dbm_open(&lockdb->info->db, lockdb->info->lockdb_path,
324                          lockdb->ro ? APR_DBM_READONLY : APR_DBM_RWCREATE,
325                          APR_OS_DEFAULT, lockdb->info->pool);
326
327    if (status) {
328        err = dav_generic_dbm_new_error(lockdb->info->db, lockdb->info->pool,
329                                        status);
330        return dav_push_error(lockdb->info->pool,
331                              HTTP_INTERNAL_SERVER_ERROR,
332                              DAV_ERR_LOCK_OPENDB,
333                              "Could not open the lock database.",
334                              err);
335    }
336
337    /* all right. it is opened now. */
338    lockdb->info->opened = 1;
339
340    return NULL;
341}
342
343/*
344 * dav_generic_open_lockdb:
345 *
346 * "open" the lock database, as specified in the global server configuration.
347 * If force is TRUE, then the database is opened now, rather than lazily.
348 *
349 * Note that only one can be open read/write.
350 */
351static dav_error * dav_generic_open_lockdb(request_rec *r, int ro, int force,
352                                           dav_lockdb **lockdb)
353{
354    dav_lockdb_combined *comb;
355
356    comb = apr_pcalloc(r->pool, sizeof(*comb));
357    comb->pub.hooks = &dav_hooks_locks_generic;
358    comb->pub.ro = ro;
359    comb->pub.info = &comb->priv;
360    comb->priv.r = r;
361    comb->priv.pool = r->pool;
362
363    comb->priv.lockdb_path = dav_generic_get_lockdb_path(r);
364    if (comb->priv.lockdb_path == NULL) {
365        return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR,
366                             DAV_ERR_LOCK_NO_DB,
367                             "A lock database was not specified with the "
368                             "DAVGenericLockDB directive. One must be "
369                             "specified to use the locking functionality.");
370    }
371
372    /* done initializing. return it. */
373    *lockdb = &comb->pub;
374
375    if (force) {
376        /* ### add a higher-level comment? */
377        return dav_generic_really_open_lockdb(*lockdb);
378    }
379
380    return NULL;
381}
382
383/*
384 * dav_generic_close_lockdb:
385 *
386 * Close it. Duh.
387 */
388static void dav_generic_close_lockdb(dav_lockdb *lockdb)
389{
390    if (lockdb->info->db != NULL) {
391        apr_dbm_close(lockdb->info->db);
392    }
393    lockdb->info->opened = 0;
394}
395
396/*
397 * dav_generic_build_key
398 *
399 * Given a pathname, build a DAV_TYPE_FNAME lock database key.
400 */
401static apr_datum_t dav_generic_build_key(apr_pool_t *p,
402                                         const dav_resource *resource)
403{
404    apr_datum_t key;
405    const char *pathname = resource->uri;
406
407    /* ### does this allocation have a proper lifetime? need to check */
408    /* ### can we use a buffer for this? */
409
410    /* size is TYPE + pathname + null */
411    key.dsize = strlen(pathname) + 2;
412    key.dptr = apr_palloc(p, key.dsize);
413    *key.dptr = DAV_TYPE_FNAME;
414    memcpy(key.dptr + 1, pathname, key.dsize - 1);
415    if (key.dptr[key.dsize - 2] == '/')
416        key.dptr[--key.dsize - 1] = '\0';
417    return key;
418}
419
420/*
421 * dav_generic_lock_expired:  return 1 (true) if the given timeout is in the
422 *    past or present (the lock has expired), or 0 (false) if in the future
423 *    (the lock has not yet expired).
424 */
425static int dav_generic_lock_expired(time_t expires)
426{
427    return expires != DAV_TIMEOUT_INFINITE && time(NULL) >= expires;
428}
429
430/*
431 * dav_generic_save_lock_record:  Saves the lock information specified in the
432 *    direct and indirect lock lists about path into the lock database.
433 *    If direct and indirect == NULL, the key is removed.
434 */
435static dav_error * dav_generic_save_lock_record(dav_lockdb *lockdb,
436                                                apr_datum_t key,
437                                                dav_lock_discovery *direct,
438                                                dav_lock_indirect *indirect)
439{
440    dav_error *err;
441    apr_status_t status;
442    apr_datum_t val = { 0 };
443    char *ptr;
444    dav_lock_discovery *dp = direct;
445    dav_lock_indirect *ip = indirect;
446
447#if DAV_DEBUG
448    if (lockdb->ro) {
449        return dav_new_error(lockdb->info->pool,
450                             HTTP_INTERNAL_SERVER_ERROR, 0,
451                             "INTERNAL DESIGN ERROR: the lockdb was opened "
452                             "readonly, but an attempt to save locks was "
453                             "performed.");
454    }
455#endif
456
457    if ((err = dav_generic_really_open_lockdb(lockdb)) != NULL) {
458        /* ### add a higher-level error? */
459        return err;
460    }
461
462    /* If nothing to save, delete key */
463    if (dp == NULL && ip == NULL) {
464        /* don't fail if the key is not present */
465        /* ### but what about other errors? */
466        apr_dbm_delete(lockdb->info->db, key);
467        return NULL;
468    }
469
470    while(dp) {
471        val.dsize += dav_size_direct(dp);
472        dp = dp->next;
473    }
474    while(ip) {
475        val.dsize += dav_size_indirect(ip);
476        ip = ip->next;
477    }
478
479    /* ### can this be apr_palloc() ? */
480    /* ### hmmm.... investigate the use of a buffer here */
481    ptr = val.dptr = apr_pcalloc(lockdb->info->pool, val.dsize);
482    dp  = direct;
483    ip  = indirect;
484
485    while(dp) {
486        /* Direct lock - lock_discovery struct follows */
487        *ptr++ = DAV_LOCK_DIRECT;
488        memcpy(ptr, dp, sizeof(dp->f));        /* Fixed portion of struct */
489        ptr += sizeof(dp->f);
490        memcpy(ptr, dp->locktoken, sizeof(*dp->locktoken));
491        ptr += sizeof(*dp->locktoken);
492        if (dp->owner == NULL) {
493            *ptr++ = '\0';
494        }
495        else {
496            memcpy(ptr, dp->owner, strlen(dp->owner) + 1);
497            ptr += strlen(dp->owner) + 1;
498        }
499        if (dp->auth_user == NULL) {
500            *ptr++ = '\0';
501        }
502        else {
503            memcpy(ptr, dp->auth_user, strlen(dp->auth_user) + 1);
504            ptr += strlen(dp->auth_user) + 1;
505        }
506
507        dp = dp->next;
508    }
509
510    while(ip) {
511        /* Indirect lock prefix */
512        *ptr++ = DAV_LOCK_INDIRECT;
513
514        memcpy(ptr, ip->locktoken, sizeof(*ip->locktoken));
515        ptr += sizeof(*ip->locktoken);
516
517        memcpy(ptr, &ip->timeout, sizeof(ip->timeout));
518        ptr += sizeof(ip->timeout);
519
520        memcpy(ptr, &ip->key.dsize, sizeof(ip->key.dsize));
521        ptr += sizeof(ip->key.dsize);
522
523        memcpy(ptr, ip->key.dptr, ip->key.dsize);
524        ptr += ip->key.dsize;
525
526        ip = ip->next;
527    }
528
529    if ((status = apr_dbm_store(lockdb->info->db, key, val)) != APR_SUCCESS) {
530        /* ### more details? add an error_id? */
531        err = dav_generic_dbm_new_error(lockdb->info->db, lockdb->info->pool,
532                                        status);
533        return dav_push_error(lockdb->info->pool,
534                              HTTP_INTERNAL_SERVER_ERROR,
535                              DAV_ERR_LOCK_SAVE_LOCK,
536                              "Could not save lock information.",
537                              err);
538    }
539
540    return NULL;
541}
542
543/*
544 * dav_load_lock_record:  Reads lock information about key from lock db;
545 *    creates linked lists of the direct and indirect locks.
546 *
547 *    If add_method = DAV_APPEND_LIST, the result will be appended to the
548 *    head of the direct and indirect lists supplied.
549 *
550 *    Passive lock removal:  If lock has timed out, it will not be returned.
551 *    ### How much "logging" does RFC 2518 require?
552 */
553static dav_error * dav_generic_load_lock_record(dav_lockdb *lockdb,
554                                                apr_datum_t key,
555                                                int add_method,
556                                                dav_lock_discovery **direct,
557                                                dav_lock_indirect **indirect)
558{
559    apr_pool_t *p = lockdb->info->pool;
560    dav_error *err;
561    apr_status_t status;
562    apr_size_t offset = 0;
563    int need_save = DAV_FALSE;
564    apr_datum_t val = { 0 };
565    dav_lock_discovery *dp;
566    dav_lock_indirect *ip;
567
568    if (add_method != DAV_APPEND_LIST) {
569        *direct = NULL;
570        *indirect = NULL;
571    }
572
573    if ((err = dav_generic_really_open_lockdb(lockdb)) != NULL) {
574        /* ### add a higher-level error? */
575        return err;
576    }
577
578    /*
579     * If we opened readonly and the db wasn't there, then there are no
580     * locks for this resource. Just exit.
581     */
582    if (lockdb->info->db == NULL) {
583        return NULL;
584    }
585
586    if ((status = apr_dbm_fetch(lockdb->info->db, key, &val)) != APR_SUCCESS) {
587        return dav_generic_dbm_new_error(lockdb->info->db, p, status);
588    }
589
590    if (!val.dsize) {
591        return NULL;
592    }
593
594    while (offset < val.dsize) {
595        switch (*(val.dptr + offset++)) {
596        case DAV_LOCK_DIRECT:
597            /* Create and fill a dav_lock_discovery structure */
598
599            dp = apr_pcalloc(p, sizeof(*dp));
600
601            /* Copy the dav_lock_discovery_fixed portion */
602            memcpy(dp, val.dptr + offset, sizeof(dp->f));
603            offset += sizeof(dp->f);
604
605            /* Copy the lock token. */
606            dp->locktoken = apr_pmemdup(p, val.dptr + offset, sizeof(*dp->locktoken));
607            offset += sizeof(*dp->locktoken);
608
609            /* Do we have an owner field? */
610            if (*(val.dptr + offset) == '\0') {
611                ++offset;
612            }
613            else {
614                apr_size_t len = strlen(val.dptr + offset);
615                dp->owner = apr_pstrmemdup(p, val.dptr + offset, len);
616                offset += len + 1;
617            }
618
619            if (*(val.dptr + offset) == '\0') {
620                ++offset;
621            }
622            else {
623                apr_size_t len = strlen(val.dptr + offset);
624                dp->auth_user = apr_pstrmemdup(p, val.dptr + offset, len);
625                offset += len + 1;
626            }
627
628            if (!dav_generic_lock_expired(dp->f.timeout)) {
629                dp->next = *direct;
630                *direct = dp;
631            }
632            else {
633                need_save = DAV_TRUE;
634            }
635            break;
636
637        case DAV_LOCK_INDIRECT:
638            /* Create and fill a dav_lock_indirect structure */
639
640            ip = apr_pcalloc(p, sizeof(*ip));
641            ip->locktoken = apr_pmemdup(p, val.dptr + offset, sizeof(*ip->locktoken));
642            offset += sizeof(*ip->locktoken);
643            memcpy(&ip->timeout, val.dptr + offset, sizeof(ip->timeout));
644            offset += sizeof(ip->timeout);
645            /* length of datum */
646            ip->key.dsize = *((int *) (val.dptr + offset));
647            offset += sizeof(ip->key.dsize);
648            ip->key.dptr = apr_pmemdup(p, val.dptr + offset, ip->key.dsize);
649            offset += ip->key.dsize;
650
651            if (!dav_generic_lock_expired(ip->timeout)) {
652                ip->next = *indirect;
653                *indirect = ip;
654            }
655            else {
656                need_save = DAV_TRUE;
657            }
658
659            break;
660
661        default:
662            apr_dbm_freedatum(lockdb->info->db, val);
663
664            /* ### should use a computed_desc and insert corrupt token data */
665            --offset;
666            return dav_new_error(p,
667                                 HTTP_INTERNAL_SERVER_ERROR,
668                                 DAV_ERR_LOCK_CORRUPT_DB,
669                                 apr_psprintf(p,
670                                             "The lock database was found to "
671                                             "be corrupt. offset %"
672                                             APR_SIZE_T_FMT ", c=%02x",
673                                             offset, val.dptr[offset]));
674        }
675    }
676
677    apr_dbm_freedatum(lockdb->info->db, val);
678
679    /* Clean up this record if we found expired locks */
680    /*
681     * ### shouldn't do this if we've been opened READONLY. elide the
682     * ### timed-out locks from the response, but don't save that info back
683     */
684    if (need_save == DAV_TRUE) {
685        return dav_generic_save_lock_record(lockdb, key, *direct, *indirect);
686    }
687
688    return NULL;
689}
690
691/* resolve <indirect>, returning <*direct> */
692static dav_error * dav_generic_resolve(dav_lockdb *lockdb,
693                                       dav_lock_indirect *indirect,
694                                       dav_lock_discovery **direct,
695                                       dav_lock_discovery **ref_dp,
696                                       dav_lock_indirect **ref_ip)
697{
698    dav_error *err;
699    dav_lock_discovery *dir;
700    dav_lock_indirect *ind;
701
702    if ((err = dav_generic_load_lock_record(lockdb, indirect->key,
703                                       DAV_CREATE_LIST,
704                                       &dir, &ind)) != NULL) {
705        /* ### insert a higher-level description? */
706        return err;
707    }
708    if (ref_dp != NULL) {
709        *ref_dp = dir;
710        *ref_ip = ind;
711    }
712
713    for (; dir != NULL; dir = dir->next) {
714        if (!dav_compare_locktoken(indirect->locktoken, dir->locktoken)) {
715            *direct = dir;
716            return NULL;
717        }
718    }
719
720    /* No match found (but we should have found one!) */
721
722    /* ### use a different description and/or error ID? */
723    return dav_new_error(lockdb->info->pool,
724                         HTTP_INTERNAL_SERVER_ERROR,
725                         DAV_ERR_LOCK_CORRUPT_DB,
726                         "The lock database was found to be corrupt. "
727                         "An indirect lock's direct lock could not "
728                         "be found.");
729}
730
731/* ---------------------------------------------------------------
732 *
733 * Property-related lock functions
734 *
735 */
736
737/*
738 * dav_generic_get_supportedlock:  Returns a static string for all
739 *    supportedlock properties. I think we save more returning a static string
740 *    than constructing it every time, though it might look cleaner.
741 */
742static const char *dav_generic_get_supportedlock(const dav_resource *resource)
743{
744    static const char supported[] = DEBUG_CR
745        "<D:lockentry>" DEBUG_CR
746        "<D:lockscope><D:exclusive/></D:lockscope>" DEBUG_CR
747        "<D:locktype><D:write/></D:locktype>" DEBUG_CR
748        "</D:lockentry>" DEBUG_CR
749        "<D:lockentry>" DEBUG_CR
750        "<D:lockscope><D:shared/></D:lockscope>" DEBUG_CR
751        "<D:locktype><D:write/></D:locktype>" DEBUG_CR
752        "</D:lockentry>" DEBUG_CR;
753
754    return supported;
755}
756
757/* ---------------------------------------------------------------
758 *
759 * General lock functions
760 *
761 */
762
763static dav_error * dav_generic_remove_locknull_state(dav_lockdb *lockdb,
764                                                 const dav_resource *resource)
765{
766    /* We don't need to do anything. */
767    return NULL;
768}
769
770static dav_error * dav_generic_create_lock(dav_lockdb *lockdb,
771                                      const dav_resource *resource,
772                                      dav_lock **lock)
773{
774    apr_datum_t key;
775
776    key = dav_generic_build_key(lockdb->info->pool, resource);
777
778    *lock = dav_generic_alloc_lock(lockdb, key, NULL);
779
780    (*lock)->is_locknull = !resource->exists;
781
782    return NULL;
783}
784
785static dav_error * dav_generic_get_locks(dav_lockdb *lockdb,
786                                         const dav_resource *resource,
787                                         int calltype,
788                                         dav_lock **locks)
789{
790    apr_pool_t *p = lockdb->info->pool;
791    apr_datum_t key;
792    dav_error *err;
793    dav_lock *lock = NULL;
794    dav_lock *newlock;
795    dav_lock_discovery *dp;
796    dav_lock_indirect *ip;
797
798#if DAV_DEBUG
799    if (calltype == DAV_GETLOCKS_COMPLETE) {
800        return dav_new_error(lockdb->info->pool,
801                             HTTP_INTERNAL_SERVER_ERROR, 0,
802                             "INTERNAL DESIGN ERROR: DAV_GETLOCKS_COMPLETE "
803                             "is not yet supported");
804    }
805#endif
806
807    key = dav_generic_build_key(p, resource);
808    if ((err = dav_generic_load_lock_record(lockdb, key, DAV_CREATE_LIST,
809                                            &dp, &ip)) != NULL) {
810        /* ### push a higher-level desc? */
811        return err;
812    }
813
814    /* copy all direct locks to the result list */
815    for (; dp != NULL; dp = dp->next) {
816        newlock = dav_generic_alloc_lock(lockdb, key, dp->locktoken);
817        newlock->is_locknull = !resource->exists;
818        newlock->scope = dp->f.scope;
819        newlock->type = dp->f.type;
820        newlock->depth = dp->f.depth;
821        newlock->timeout = dp->f.timeout;
822        newlock->owner = dp->owner;
823        newlock->auth_user = dp->auth_user;
824
825        /* hook into the result list */
826        newlock->next = lock;
827        lock = newlock;
828    }
829
830    /* copy all the indirect locks to the result list. resolve as needed. */
831    for (; ip != NULL; ip = ip->next) {
832        newlock = dav_generic_alloc_lock(lockdb, ip->key, ip->locktoken);
833        newlock->is_locknull = !resource->exists;
834
835        if (calltype == DAV_GETLOCKS_RESOLVED) {
836            err = dav_generic_resolve(lockdb, ip, &dp, NULL, NULL);
837            if (err != NULL) {
838                /* ### push a higher-level desc? */
839                return err;
840            }
841
842            newlock->scope = dp->f.scope;
843            newlock->type = dp->f.type;
844            newlock->depth = dp->f.depth;
845            newlock->timeout = dp->f.timeout;
846            newlock->owner = dp->owner;
847            newlock->auth_user = dp->auth_user;
848        }
849        else {
850            /* DAV_GETLOCKS_PARTIAL */
851            newlock->rectype = DAV_LOCKREC_INDIRECT_PARTIAL;
852        }
853
854        /* hook into the result list */
855        newlock->next = lock;
856        lock = newlock;
857    }
858
859    *locks = lock;
860    return NULL;
861}
862
863static dav_error * dav_generic_find_lock(dav_lockdb *lockdb,
864                                         const dav_resource *resource,
865                                         const dav_locktoken *locktoken,
866                                         int partial_ok,
867                                         dav_lock **lock)
868{
869    dav_error *err;
870    apr_datum_t key;
871    dav_lock_discovery *dp;
872    dav_lock_indirect *ip;
873
874    *lock = NULL;
875
876    key = dav_generic_build_key(lockdb->info->pool, resource);
877    if ((err = dav_generic_load_lock_record(lockdb, key, DAV_CREATE_LIST,
878                                       &dp, &ip)) != NULL) {
879        /* ### push a higher-level desc? */
880        return err;
881    }
882
883    for (; dp != NULL; dp = dp->next) {
884        if (!dav_compare_locktoken(locktoken, dp->locktoken)) {
885            *lock = dav_generic_alloc_lock(lockdb, key, locktoken);
886            (*lock)->is_locknull = !resource->exists;
887            (*lock)->scope = dp->f.scope;
888            (*lock)->type = dp->f.type;
889            (*lock)->depth = dp->f.depth;
890            (*lock)->timeout = dp->f.timeout;
891            (*lock)->owner = dp->owner;
892            (*lock)->auth_user = dp->auth_user;
893            return NULL;
894        }
895    }
896
897    for (; ip != NULL; ip = ip->next) {
898        if (!dav_compare_locktoken(locktoken, ip->locktoken)) {
899            *lock = dav_generic_alloc_lock(lockdb, ip->key, locktoken);
900            (*lock)->is_locknull = !resource->exists;
901
902            /* ### nobody uses the resolving right now! */
903            if (partial_ok) {
904                (*lock)->rectype = DAV_LOCKREC_INDIRECT_PARTIAL;
905            }
906            else {
907                (*lock)->rectype = DAV_LOCKREC_INDIRECT;
908                if ((err = dav_generic_resolve(lockdb, ip, &dp,
909                                          NULL, NULL)) != NULL) {
910                    /* ### push a higher-level desc? */
911                    return err;
912                }
913                (*lock)->scope = dp->f.scope;
914                (*lock)->type = dp->f.type;
915                (*lock)->depth = dp->f.depth;
916                (*lock)->timeout = dp->f.timeout;
917                (*lock)->owner = dp->owner;
918                (*lock)->auth_user = dp->auth_user;
919            }
920            return NULL;
921        }
922    }
923
924    return NULL;
925}
926
927static dav_error * dav_generic_has_locks(dav_lockdb *lockdb,
928                                         const dav_resource *resource,
929                                         int *locks_present)
930{
931    dav_error *err;
932    apr_datum_t key;
933
934    *locks_present = 0;
935
936    if ((err = dav_generic_really_open_lockdb(lockdb)) != NULL) {
937        /* ### insert a higher-level error description */
938        return err;
939    }
940
941    /*
942     * If we opened readonly and the db wasn't there, then there are no
943     * locks for this resource. Just exit.
944     */
945    if (lockdb->info->db == NULL)
946        return NULL;
947
948    key = dav_generic_build_key(lockdb->info->pool, resource);
949
950    *locks_present = apr_dbm_exists(lockdb->info->db, key);
951
952    return NULL;
953}
954
955static dav_error * dav_generic_append_locks(dav_lockdb *lockdb,
956                                            const dav_resource *resource,
957                                            int make_indirect,
958                                            const dav_lock *lock)
959{
960    apr_pool_t *p = lockdb->info->pool;
961    dav_error *err;
962    dav_lock_indirect *ip;
963    dav_lock_discovery *dp;
964    apr_datum_t key;
965
966    key = dav_generic_build_key(lockdb->info->pool, resource);
967
968    err = dav_generic_load_lock_record(lockdb, key, 0, &dp, &ip);
969    if (err != NULL) {
970        /* ### maybe add in a higher-level description */
971        return err;
972    }
973
974    /*
975     * ### when we store the lock more directly, we need to update
976     * ### lock->rectype and lock->is_locknull
977     */
978
979    if (make_indirect) {
980        for (; lock != NULL; lock = lock->next) {
981
982            /* ### this works for any <lock> rectype */
983            dav_lock_indirect *newi = apr_pcalloc(p, sizeof(*newi));
984
985            /* ### shut off the const warning for now */
986            newi->locktoken = (dav_locktoken *)lock->locktoken;
987            newi->timeout   = lock->timeout;
988            newi->key       = lock->info->key;
989            newi->next      = ip;
990            ip              = newi;
991        }
992    }
993    else {
994        for (; lock != NULL; lock = lock->next) {
995            /* create and link in the right kind of lock */
996
997            if (lock->rectype == DAV_LOCKREC_DIRECT) {
998                dav_lock_discovery *newd = apr_pcalloc(p, sizeof(*newd));
999
1000                newd->f.scope = lock->scope;
1001                newd->f.type = lock->type;
1002                newd->f.depth = lock->depth;
1003                newd->f.timeout = lock->timeout;
1004                /* ### shut off the const warning for now */
1005                newd->locktoken = (dav_locktoken *)lock->locktoken;
1006                newd->owner = lock->owner;
1007                newd->auth_user = lock->auth_user;
1008                newd->next = dp;
1009                dp = newd;
1010            }
1011            else {
1012                /* DAV_LOCKREC_INDIRECT(_PARTIAL) */
1013
1014                dav_lock_indirect *newi = apr_pcalloc(p, sizeof(*newi));
1015
1016                /* ### shut off the const warning for now */
1017                newi->locktoken = (dav_locktoken *)lock->locktoken;
1018                newi->key       = lock->info->key;
1019                newi->next      = ip;
1020                ip              = newi;
1021            }
1022        }
1023    }
1024
1025    if ((err = dav_generic_save_lock_record(lockdb, key, dp, ip)) != NULL) {
1026        /* ### maybe add a higher-level description */
1027        return err;
1028    }
1029
1030    return NULL;
1031}
1032
1033static dav_error * dav_generic_remove_lock(dav_lockdb *lockdb,
1034                                           const dav_resource *resource,
1035                                           const dav_locktoken *locktoken)
1036{
1037    dav_error *err;
1038    dav_lock_discovery *dh = NULL;
1039    dav_lock_indirect *ih = NULL;
1040    apr_datum_t key;
1041
1042    key = dav_generic_build_key(lockdb->info->pool, resource);
1043
1044    if (locktoken != NULL) {
1045        dav_lock_discovery *dp;
1046        dav_lock_discovery *dprev = NULL;
1047        dav_lock_indirect *ip;
1048        dav_lock_indirect *iprev = NULL;
1049
1050        if ((err = dav_generic_load_lock_record(lockdb, key, DAV_CREATE_LIST,
1051                                           &dh, &ih)) != NULL) {
1052            /* ### maybe add a higher-level description */
1053            return err;
1054        }
1055
1056        for (dp = dh; dp != NULL; dp = dp->next) {
1057            if (dav_compare_locktoken(locktoken, dp->locktoken) == 0) {
1058                if (dprev)
1059                    dprev->next = dp->next;
1060                else
1061                    dh = dh->next;
1062            }
1063            dprev = dp;
1064        }
1065
1066        for (ip = ih; ip != NULL; ip = ip->next) {
1067            if (dav_compare_locktoken(locktoken, ip->locktoken) == 0) {
1068                if (iprev)
1069                    iprev->next = ip->next;
1070                else
1071                    ih = ih->next;
1072            }
1073            iprev = ip;
1074        }
1075
1076    }
1077
1078    /* save the modified locks, or remove all locks (dh=ih=NULL). */
1079    if ((err = dav_generic_save_lock_record(lockdb, key, dh, ih)) != NULL) {
1080        /* ### maybe add a higher-level description */
1081        return err;
1082    }
1083
1084    return NULL;
1085}
1086
1087static int dav_generic_do_refresh(dav_lock_discovery *dp,
1088                                  const dav_locktoken_list *ltl,
1089                                  time_t new_time)
1090{
1091    int dirty = 0;
1092
1093    for (; ltl != NULL; ltl = ltl->next) {
1094        if (dav_compare_locktoken(dp->locktoken, ltl->locktoken) == 0)
1095        {
1096            dp->f.timeout = new_time;
1097            dirty = 1;
1098        }
1099    }
1100
1101    return dirty;
1102}
1103
1104static dav_error * dav_generic_refresh_locks(dav_lockdb *lockdb,
1105                                             const dav_resource *resource,
1106                                             const dav_locktoken_list *ltl,
1107                                             time_t new_time,
1108                                             dav_lock **locks)
1109{
1110    dav_error *err;
1111    apr_datum_t key;
1112    dav_lock_discovery *dp;
1113    dav_lock_discovery *dp_scan;
1114    dav_lock_indirect *ip;
1115    int dirty = 0;
1116    dav_lock *newlock;
1117
1118    *locks = NULL;
1119
1120    key = dav_generic_build_key(lockdb->info->pool, resource);
1121    if ((err = dav_generic_load_lock_record(lockdb, key, DAV_CREATE_LIST,
1122                                            &dp, &ip)) != NULL) {
1123        /* ### maybe add in a higher-level description */
1124        return err;
1125    }
1126
1127    /* ### we should be refreshing direct AND (resolved) indirect locks! */
1128
1129    /* refresh all of the direct locks on this resource */
1130    for (dp_scan = dp; dp_scan != NULL; dp_scan = dp_scan->next) {
1131        if (dav_generic_do_refresh(dp_scan, ltl, new_time)) {
1132            /* the lock was refreshed. return the lock. */
1133            newlock = dav_generic_alloc_lock(lockdb, key, dp_scan->locktoken);
1134            newlock->is_locknull = !resource->exists;
1135            newlock->scope = dp_scan->f.scope;
1136            newlock->type = dp_scan->f.type;
1137            newlock->depth = dp_scan->f.depth;
1138            newlock->timeout = dp_scan->f.timeout;
1139            newlock->owner = dp_scan->owner;
1140            newlock->auth_user = dp_scan->auth_user;
1141
1142            newlock->next = *locks;
1143            *locks = newlock;
1144
1145            dirty = 1;
1146        }
1147    }
1148
1149    /* if we refreshed any locks, then save them back. */
1150    if (dirty
1151        && (err = dav_generic_save_lock_record(lockdb, key, dp, ip)) != NULL) {
1152        /* ### maybe add in a higher-level description */
1153        return err;
1154    }
1155
1156    /* for each indirect lock, find its direct lock and refresh it. */
1157    for (; ip != NULL; ip = ip->next) {
1158        dav_lock_discovery *ref_dp;
1159        dav_lock_indirect *ref_ip;
1160
1161        if ((err = dav_generic_resolve(lockdb, ip, &dp_scan,
1162                                       &ref_dp, &ref_ip)) != NULL) {
1163            /* ### push a higher-level desc? */
1164            return err;
1165        }
1166        if (dav_generic_do_refresh(dp_scan, ltl, new_time)) {
1167            /* the lock was refreshed. return the lock. */
1168            newlock = dav_generic_alloc_lock(lockdb, ip->key, dp->locktoken);
1169            newlock->is_locknull = !resource->exists;
1170            newlock->scope = dp->f.scope;
1171            newlock->type = dp->f.type;
1172            newlock->depth = dp->f.depth;
1173            newlock->timeout = dp->f.timeout;
1174            newlock->owner = dp->owner;
1175            newlock->auth_user = dp_scan->auth_user;
1176
1177            newlock->next = *locks;
1178            *locks = newlock;
1179
1180            /* save the (resolved) direct lock back */
1181            if ((err = dav_generic_save_lock_record(lockdb, ip->key, ref_dp,
1182                                                    ref_ip)) != NULL) {
1183                /* ### push a higher-level desc? */
1184                return err;
1185            }
1186        }
1187    }
1188
1189    return NULL;
1190}
1191
1192
1193const dav_hooks_locks dav_hooks_locks_generic =
1194{
1195    dav_generic_get_supportedlock,
1196    dav_generic_parse_locktoken,
1197    dav_generic_format_locktoken,
1198    dav_generic_compare_locktoken,
1199    dav_generic_open_lockdb,
1200    dav_generic_close_lockdb,
1201    dav_generic_remove_locknull_state,
1202    dav_generic_create_lock,
1203    dav_generic_get_locks,
1204    dav_generic_find_lock,
1205    dav_generic_has_locks,
1206    dav_generic_append_locks,
1207    dav_generic_remove_lock,
1208    dav_generic_refresh_locks,
1209    NULL, /* lookup_resource */
1210
1211    NULL /* ctx */
1212};
1213