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