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