Imported Upstream version 3.13.6
[platform/upstream/nss.git] / mozilla / security / nss / lib / softoken / legacydb / dbmshim.c
1 /* ***** BEGIN LICENSE BLOCK *****
2  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3  *
4  * The contents of this file are subject to the Mozilla Public License Version
5  * 1.1 (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  * http://www.mozilla.org/MPL/
8  *
9  * Software distributed under the License is distributed on an "AS IS" basis,
10  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11  * for the specific language governing rights and limitations under the
12  * License.
13  *
14  * The Original Code is the Netscape security libraries.
15  *
16  * The Initial Developer of the Original Code is
17  * Netscape Communications Corporation.
18  * Portions created by the Initial Developer are Copyright (C) 1994-2000
19  * the Initial Developer. All Rights Reserved.
20  *
21  * Contributor(s):
22  *
23  * Alternatively, the contents of this file may be used under the terms of
24  * either the GNU General Public License Version 2 or later (the "GPL"), or
25  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
26  * in which case the provisions of the GPL or the LGPL are applicable instead
27  * of those above. If you wish to allow use of your version of this file only
28  * under the terms of either the GPL or the LGPL, and not to allow others to
29  * use your version of this file under the terms of the MPL, indicate your
30  * decision by deleting the provisions above and replace them with the notice
31  * and other provisions required by the GPL or the LGPL. If you do not delete
32  * the provisions above, a recipient may use your version of this file under
33  * the terms of any one of the MPL, the GPL or the LGPL.
34  *
35  * ***** END LICENSE BLOCK ***** */
36
37 /*
38  * Berkeley DB 1.85 Shim code to handle blobs.
39  *
40  * $Id: dbmshim.c,v 1.2 2007/06/13 00:24:57 rrelyea%redhat.com Exp $
41  */
42 #include "mcom_db.h"
43 #include "secitem.h"
44 #include "nssb64.h"
45 #include "blapi.h"
46 #include "secerr.h"
47
48 #include "lgdb.h"
49
50 /*
51  *   Blob block:
52  *   Byte 0   CERTDB Version           -+                       -+
53  *   Byte 1   certDBEntryTypeBlob       |  BLOB_HEAD_LEN         |
54  *   Byte 2   flags (always '0');       |                        |
55  *   Byte 3   reserved (always '0');   -+                        |
56  *   Byte 4   LSB length                | <--BLOB_LENGTH_START   | BLOB_BUF_LEN
57  *   Byte 5       .                     |                        |
58  *   Byte 6       .                     | BLOB_LENGTH_LEN        |
59  *   Byte 7   MSB length                |                        |
60  *   Byte 8   blob_filename   -+       -+  <-- BLOB_NAME_START   |
61  *   Byte 9       .            | BLOB_NAME_LEN                   |
62  *     .          .            |                                 |
63  *   Byte 37      .           -+                                -+
64  */
65 #define DBS_BLOCK_SIZE (16*1024) /* 16 k */
66 #define DBS_MAX_ENTRY_SIZE (DBS_BLOCK_SIZE - (2048)) /* 14 k */
67 #define DBS_CACHE_SIZE  DBS_BLOCK_SIZE*8
68 #define ROUNDDIV(x,y) (x+(y-1))/y
69 #define BLOB_HEAD_LEN 4
70 #define BLOB_LENGTH_START BLOB_HEAD_LEN
71 #define BLOB_LENGTH_LEN 4
72 #define BLOB_NAME_START BLOB_LENGTH_START+BLOB_LENGTH_LEN
73 #define BLOB_NAME_LEN 1+ROUNDDIV(SHA1_LENGTH,3)*4+1
74 #define BLOB_BUF_LEN BLOB_HEAD_LEN+BLOB_LENGTH_LEN+BLOB_NAME_LEN
75
76 /* a Shim data structure. This data structure has a db built into it. */
77 typedef struct DBSStr DBS;
78
79 struct DBSStr {
80     DB db;
81     char *blobdir;
82     int mode;
83     PRBool readOnly;
84     PRFileMap *dbs_mapfile;
85     unsigned char *dbs_addr;
86     PRUint32 dbs_len;
87     char staticBlobArea[BLOB_BUF_LEN];
88 };
89     
90     
91
92 /*
93  * return true if the Datablock contains a blobtype
94  */
95 static PRBool
96 dbs_IsBlob(DBT *blobData)
97 {
98     unsigned char *addr = (unsigned char *)blobData->data;
99     if (blobData->size < BLOB_BUF_LEN) {
100         return PR_FALSE;
101     }
102     return addr && ((certDBEntryType) addr[1] == certDBEntryTypeBlob);
103 }
104
105 /*
106  * extract the filename in the blob of the real data set.
107  * This value is not malloced (does not need to be freed by the caller.
108  */
109 static const char *
110 dbs_getBlobFileName(DBT *blobData)
111 {
112     char *addr = (char *)blobData->data;
113
114     return &addr[BLOB_NAME_START];
115 }
116
117 /*
118  * extract the size of the actual blob from the blob record
119  */
120 static PRUint32
121 dbs_getBlobSize(DBT *blobData)
122 {
123     unsigned char *addr = (unsigned char *)blobData->data;
124
125     return (PRUint32)(addr[BLOB_LENGTH_START+3] << 24) | 
126                         (addr[BLOB_LENGTH_START+2] << 16) | 
127                         (addr[BLOB_LENGTH_START+1] << 8) | 
128                         addr[BLOB_LENGTH_START];
129 }
130
131
132 /* We are using base64 data for the filename, but base64 data can include a
133  * '/' which is interpreted as a path separator on  many platforms. Replace it
134  * with an inocuous '-'. We don't need to convert back because we never actual
135  * decode the filename.
136  */
137
138 static void
139 dbs_replaceSlash(char *cp, int len)
140 {
141    while (len--) {
142         if (*cp == '/') *cp = '-';
143         cp++;
144    }
145 }
146
147 /*
148  * create a blob record from a key, data and return it in blobData.
149  * NOTE: The data element is static data (keeping with the dbm model).
150  */
151 static void
152 dbs_mkBlob(DBS *dbsp,const DBT *key, const DBT *data, DBT *blobData)
153 {
154    unsigned char sha1_data[SHA1_LENGTH];
155    char *b = dbsp->staticBlobArea;
156    PRUint32 length = data->size;
157    SECItem sha1Item;
158
159    b[0] = CERT_DB_FILE_VERSION; /* certdb version number */
160    b[1] = (char) certDBEntryTypeBlob; /* type */
161    b[2] = 0; /* flags */
162    b[3] = 0; /* reserved */
163    b[BLOB_LENGTH_START] = length & 0xff;
164    b[BLOB_LENGTH_START+1] = (length >> 8) & 0xff;
165    b[BLOB_LENGTH_START+2] = (length >> 16) & 0xff;
166    b[BLOB_LENGTH_START+3] = (length >> 24) & 0xff;
167    sha1Item.data = sha1_data;
168    sha1Item.len = SHA1_LENGTH;
169    SHA1_HashBuf(sha1_data,key->data,key->size);
170    b[BLOB_NAME_START]='b'; /* Make sure we start with a alpha */
171    NSSBase64_EncodeItem(NULL,&b[BLOB_NAME_START+1],BLOB_NAME_LEN-1,&sha1Item);
172    b[BLOB_BUF_LEN-1] = 0;
173    dbs_replaceSlash(&b[BLOB_NAME_START+1],BLOB_NAME_LEN-1);
174    blobData->data = b;
175    blobData->size = BLOB_BUF_LEN;
176    return;
177 }
178    
179
180 /*
181  * construct a path to the actual blob. The string returned must be
182  * freed by the caller with PR_smprintf_free.
183  *
184  * Note: this file does lots of consistancy checks on the DBT. The
185  * routines that call this depend on these checks, so they don't worry
186  * about them (success of this routine implies a good blobdata record).
187  */ 
188 static char *
189 dbs_getBlobFilePath(char *blobdir,DBT *blobData)
190 {
191     const char *name;
192
193     if (blobdir == NULL) {
194         PR_SetError(SEC_ERROR_BAD_DATABASE,0);
195         return NULL;
196     }
197     if (!dbs_IsBlob(blobData)) {
198         PR_SetError(SEC_ERROR_BAD_DATABASE,0);
199         return NULL;
200     }
201     name = dbs_getBlobFileName(blobData);
202     if (!name || *name == 0) {
203         PR_SetError(SEC_ERROR_BAD_DATABASE,0);
204         return NULL;
205     }
206     return  PR_smprintf("%s" PATH_SEPARATOR "%s", blobdir, name);
207 }
208
209 /*
210  * Delete a blob file pointed to by the blob record.
211  */
212 static void
213 dbs_removeBlob(DBS *dbsp, DBT *blobData)
214 {
215     char *file;
216
217     file = dbs_getBlobFilePath(dbsp->blobdir, blobData);
218     if (!file) {
219         return;
220     }
221     PR_Delete(file);
222     PR_smprintf_free(file);
223 }
224
225 /*
226  * Directory modes are slightly different, the 'x' bit needs to be on to
227  * access them. Copy all the read bits to 'x' bits
228  */
229 static int
230 dbs_DirMode(int mode)
231 {
232   int x_bits = (mode >> 2) &  0111;
233   return mode | x_bits;
234 }
235
236 /*
237  * write a data blob to it's file. blobdData is the blob record that will be
238  * stored in the database. data is the actual data to go out on disk.
239  */
240 static int
241 dbs_writeBlob(DBS *dbsp, int mode, DBT *blobData, const DBT *data)
242 {
243     char *file = NULL;
244     PRFileDesc *filed;
245     PRStatus status;
246     int len;
247     int error = 0;
248
249     file = dbs_getBlobFilePath(dbsp->blobdir, blobData);
250     if (!file) {
251         goto loser;
252     }
253     if (PR_Access(dbsp->blobdir, PR_ACCESS_EXISTS) != PR_SUCCESS) {
254         status = PR_MkDir(dbsp->blobdir,dbs_DirMode(mode));
255         if (status != PR_SUCCESS) {
256             goto loser;
257         }
258     }
259     filed = PR_OpenFile(file,PR_CREATE_FILE|PR_TRUNCATE|PR_WRONLY, mode);
260     if (filed == NULL) {
261         error = PR_GetError();
262         goto loser;
263     }
264     len = PR_Write(filed,data->data,data->size);
265     error = PR_GetError();
266     PR_Close(filed);
267     if (len < (int)data->size) {
268         goto loser;
269     }
270     PR_smprintf_free(file);
271     return 0;
272
273 loser:
274     if (file) {
275         PR_Delete(file);
276         PR_smprintf_free(file);
277     }
278     /* don't let close or delete reset the error */
279     PR_SetError(error,0);
280     return -1;
281 }
282
283
284 /*
285  * we need to keep a address map in memory between calls to DBM.
286  * remember what we have mapped can close it when we get another dbm
287  * call. 
288  *
289  * NOTE: Not all platforms support mapped files. This code is designed to
290  * detect this at runtime. If map files aren't supported the OS will indicate
291  * this by failing the PR_Memmap call. In this case we emulate mapped files
292  * by just reading in the file into regular memory. We signal this state by
293  * making dbs_mapfile NULL and dbs_addr non-NULL.
294  */
295
296 static void
297 dbs_freemap(DBS *dbsp)
298 {
299     if (dbsp->dbs_mapfile) {
300         PR_MemUnmap(dbsp->dbs_addr,dbsp->dbs_len);
301         PR_CloseFileMap(dbsp->dbs_mapfile);
302         dbsp->dbs_mapfile = NULL;
303         dbsp->dbs_addr = NULL;
304         dbsp->dbs_len = 0;
305     } else if (dbsp->dbs_addr) {
306         PORT_Free(dbsp->dbs_addr);
307         dbsp->dbs_addr = NULL;
308         dbsp->dbs_len = 0;
309     }
310     return;
311 }
312
313 static void
314 dbs_setmap(DBS *dbsp, PRFileMap *mapfile, unsigned char *addr, PRUint32 len)
315 {
316     dbsp->dbs_mapfile = mapfile;
317     dbsp->dbs_addr = addr;
318     dbsp->dbs_len = len;
319 }
320
321 /*
322  * platforms that cannot map the file need to read it into a temp buffer.
323  */
324 static unsigned char *
325 dbs_EmulateMap(PRFileDesc *filed, int len)
326 {
327     unsigned char *addr;
328     PRInt32 dataRead;
329
330     addr = PORT_Alloc(len);
331     if (addr == NULL) {
332         return NULL;
333     }
334
335     dataRead = PR_Read(filed,addr,len);
336     if (dataRead != len) {
337         PORT_Free(addr);
338         if (dataRead > 0) {
339             /* PR_Read didn't set an error, we need to */
340             PR_SetError(SEC_ERROR_BAD_DATABASE,0);
341         }
342         return NULL;
343     }
344
345     return addr;
346 }
347
348
349 /*
350  * pull a database record off the disk
351  * data points to the blob record on input and the real record (if we could
352  * read it) on output. if there is an error data is not modified.
353  */
354 static int
355 dbs_readBlob(DBS *dbsp, DBT *data)
356 {
357     char *file = NULL;
358     PRFileDesc *filed = NULL;
359     PRFileMap *mapfile = NULL;
360     unsigned char *addr = NULL;
361     int error;
362     int len = -1;
363
364     file = dbs_getBlobFilePath(dbsp->blobdir, data);
365     if (!file) {
366         goto loser;
367     }
368     filed = PR_OpenFile(file,PR_RDONLY,0);
369     PR_smprintf_free(file); file = NULL;
370     if (filed == NULL) {
371         goto loser;
372     }
373
374     len = dbs_getBlobSize(data);
375     mapfile = PR_CreateFileMap(filed, len, PR_PROT_READONLY);
376     if (mapfile == NULL) {
377          /* USE PR_GetError instead of PORT_GetError here
378           * because we are getting the error from PR_xxx
379           * function */
380         if (PR_GetError() != PR_NOT_IMPLEMENTED_ERROR) {
381             goto loser;
382         }
383         addr = dbs_EmulateMap(filed, len);
384     } else {
385         addr = PR_MemMap(mapfile, 0, len);
386     }
387     if (addr == NULL) {
388         goto loser;
389     }
390     PR_Close(filed);
391     dbs_setmap(dbsp,mapfile,addr,len);
392
393     data->data = addr;
394     data->size = len;
395     return 0;
396
397 loser:
398     /* preserve the error code */
399     error = PR_GetError();
400     if (mapfile) {
401         PR_CloseFileMap(mapfile);
402     }
403     if (filed) {
404         PR_Close(filed);
405     }
406     PR_SetError(error,0);
407     return -1;
408 }
409
410 /*
411  * actual DBM shims
412  */
413 static int
414 dbs_get(const DB *dbs, const DBT *key, DBT *data, unsigned int flags)
415 {
416     int ret;
417     DBS *dbsp = (DBS *)dbs;
418     DB *db = (DB *)dbs->internal;
419     
420
421     dbs_freemap(dbsp);
422     
423     ret = (* db->get)(db, key, data, flags);
424     if ((ret == 0) && dbs_IsBlob(data)) {
425         ret = dbs_readBlob(dbsp,data);
426     }
427
428     return(ret);
429 }
430
431 static int
432 dbs_put(const DB *dbs, DBT *key, const DBT *data, unsigned int flags)
433 {
434     DBT blob;
435     int ret = 0;
436     DBS *dbsp = (DBS *)dbs;
437     DB *db = (DB *)dbs->internal;
438
439     dbs_freemap(dbsp);
440
441     /* If the db is readonly, just pass the data down to rdb and let it fail */
442     if (!dbsp->readOnly) {
443         DBT oldData;
444         int ret1;
445
446         /* make sure the current record is deleted if it's a blob */
447         ret1 = (*db->get)(db,key,&oldData,0);
448         if ((ret1 == 0) && flags == R_NOOVERWRITE) {
449             /* let DBM return the error to maintain consistancy */
450             return (* db->put)(db, key, data, flags);
451         }
452         if ((ret1 == 0) && dbs_IsBlob(&oldData)) {
453             dbs_removeBlob(dbsp, &oldData);
454         }
455
456         if (data->size > DBS_MAX_ENTRY_SIZE) {
457             dbs_mkBlob(dbsp,key,data,&blob);
458             ret = dbs_writeBlob(dbsp, dbsp->mode, &blob, data);
459             data = &blob;
460         }
461     }
462
463     if (ret == 0) {
464         ret = (* db->put)(db, key, data, flags);
465     }
466     return(ret);
467 }
468
469 static int
470 dbs_sync(const DB *dbs, unsigned int flags)
471 {
472     DB *db = (DB *)dbs->internal;
473     DBS *dbsp = (DBS *)dbs;
474
475     dbs_freemap(dbsp);
476
477     return (* db->sync)(db, flags);
478 }
479
480 static int
481 dbs_del(const DB *dbs, const DBT *key, unsigned int flags)
482 {
483     int ret;
484     DBS *dbsp = (DBS *)dbs;
485     DB *db = (DB *)dbs->internal;
486
487     dbs_freemap(dbsp);
488
489     if (!dbsp->readOnly) {
490         DBT oldData;
491         ret = (*db->get)(db,key,&oldData,0);
492         if ((ret == 0) && dbs_IsBlob(&oldData)) {
493             dbs_removeBlob(dbsp,&oldData);
494         }
495     }
496
497     return (* db->del)(db, key, flags);
498 }
499
500 static int
501 dbs_seq(const DB *dbs, DBT *key, DBT *data, unsigned int flags)
502 {
503     int ret;
504     DBS *dbsp = (DBS *)dbs;
505     DB *db = (DB *)dbs->internal;
506     
507     dbs_freemap(dbsp);
508     
509     ret = (* db->seq)(db, key, data, flags);
510     if ((ret == 0) && dbs_IsBlob(data)) {
511         /* don't return a blob read as an error so traversals keep going */
512         (void) dbs_readBlob(dbsp,data);
513     }
514
515     return(ret);
516 }
517
518 static int
519 dbs_close(DB *dbs)
520 {
521     DBS *dbsp = (DBS *)dbs;
522     DB *db = (DB *)dbs->internal;
523     int ret;
524
525     dbs_freemap(dbsp);
526     ret = (* db->close)(db);
527     PORT_Free(dbsp->blobdir);
528     PORT_Free(dbsp);
529     return ret;
530 }
531
532 static int
533 dbs_fd(const DB *dbs)
534 {
535     DB *db = (DB *)dbs->internal;
536
537     return (* db->fd)(db);
538 }
539
540 /*
541  * the naming convention we use is
542  * change the .xxx into .dir. (for nss it's always .db);
543  * if no .extension exists or is equal to .dir, add a .dir 
544  * the returned data must be freed.
545  */
546 #define DIRSUFFIX ".dir"
547 static char *
548 dbs_mkBlobDirName(const char *dbname)
549 {
550     int dbname_len = PORT_Strlen(dbname);
551     int dbname_end = dbname_len;
552     const char *cp;
553     char *blobDir = NULL;
554
555     /* scan back from the end looking for either a directory separator, a '.',
556      * or the end of the string. NOTE: Windows should check for both separators
557      * here. For now this is safe because we know NSS always uses a '.'
558      */
559     for (cp = &dbname[dbname_len]; 
560                 (cp > dbname) && (*cp != '.') && (*cp != *PATH_SEPARATOR) ;
561                         cp--)
562         /* Empty */ ;
563     if (*cp == '.') {
564         dbname_end = cp - dbname;
565         if (PORT_Strcmp(cp,DIRSUFFIX) == 0) {
566             dbname_end = dbname_len;
567         }
568     }
569     blobDir = PORT_ZAlloc(dbname_end+sizeof(DIRSUFFIX));
570     if (blobDir == NULL) {
571         return NULL;
572     }
573     PORT_Memcpy(blobDir,dbname,dbname_end);
574     PORT_Memcpy(&blobDir[dbname_end],DIRSUFFIX,sizeof(DIRSUFFIX));
575     return blobDir;
576 }
577
578 #define DBM_DEFAULT 0
579 static const HASHINFO dbs_hashInfo = {
580         DBS_BLOCK_SIZE,         /* bucket size, must be greater than = to
581                                  * or maximum entry size (+ header)
582                                  * we allow before blobing */
583         DBM_DEFAULT,            /* Fill Factor */
584         DBM_DEFAULT,            /* number of elements */
585         DBS_CACHE_SIZE,         /* cache size */
586         DBM_DEFAULT,            /* hash function */
587         DBM_DEFAULT,            /* byte order */
588 };
589
590 /*
591  * the open function. NOTE: this is the only exposed function in this file.
592  * everything else is called through the function table pointer.
593  */
594 DB *
595 dbsopen(const char *dbname, int flags, int mode, DBTYPE type,
596                                                          const void *userData)
597 {
598     DB *db = NULL,*dbs = NULL;
599     DBS *dbsp = NULL;
600
601     /* NOTE: we are overriding userData with dbs_hashInfo. since all known
602      * callers pass 0, this is ok, otherwise we should merge the two */
603
604     dbsp = (DBS *)PORT_ZAlloc(sizeof(DBS));
605     if (!dbsp) {
606         return NULL;
607     }
608     dbs = &dbsp->db;
609
610     dbsp->blobdir=dbs_mkBlobDirName(dbname);
611     if (dbsp->blobdir == NULL) {
612         goto loser;
613     }
614     dbsp->mode = mode;
615     dbsp->readOnly = (PRBool)(flags == NO_RDONLY);
616     dbsp->dbs_mapfile = NULL;
617     dbsp->dbs_addr = NULL;
618     dbsp->dbs_len = 0;
619
620     /* the real dbm call */
621     db = dbopen(dbname, flags, mode, type, &dbs_hashInfo);
622     if (db == NULL) {
623         goto loser;
624     }
625     dbs->internal = (void *) db;
626     dbs->type = type;
627     dbs->close = dbs_close;
628     dbs->get = dbs_get;
629     dbs->del = dbs_del;
630     dbs->put = dbs_put;
631     dbs->seq = dbs_seq;
632     dbs->sync = dbs_sync;
633     dbs->fd = dbs_fd;
634
635     return dbs;
636 loser:
637     if (db) {
638         (*db->close)(db);
639     }
640     if (dbsp) {
641         if (dbsp->blobdir) {
642             PORT_Free(dbsp->blobdir);
643         }
644         PORT_Free(dbsp);
645     }
646     return NULL;
647 }