dbmshim.c (14288B)
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 char staticBlobArea[BLOB_BUF_LEN]; 51 }; 52 53 /* 54 * return true if the Datablock contains a blobtype 55 */ 56 static PRBool 57 dbs_IsBlob(DBT *blobData) 58 { 59 unsigned char *addr = (unsigned char *)blobData->data; 60 if (blobData->size < BLOB_BUF_LEN) { 61 return PR_FALSE; 62 } 63 return addr && ((certDBEntryType)addr[1] == certDBEntryTypeBlob); 64 } 65 66 /* 67 * extract the filename in the blob of the real data set. 68 * This value is not malloced (does not need to be freed by the caller. 69 */ 70 static const char * 71 dbs_getBlobFileName(DBT *blobData) 72 { 73 char *addr = (char *)blobData->data; 74 75 return &addr[BLOB_NAME_START]; 76 } 77 78 /* 79 * extract the size of the actual blob from the blob record 80 */ 81 static PRUint32 82 dbs_getBlobSize(DBT *blobData) 83 { 84 unsigned char *addr = (unsigned char *)blobData->data; 85 86 return (PRUint32)(addr[BLOB_LENGTH_START + 3] << 24) | 87 (addr[BLOB_LENGTH_START + 2] << 16) | 88 (addr[BLOB_LENGTH_START + 1] << 8) | 89 addr[BLOB_LENGTH_START]; 90 } 91 92 /* We are using base64 data for the filename, but base64 data can include a 93 * '/' which is interpreted as a path separator on many platforms. Replace it 94 * with an inocuous '-'. We don't need to convert back because we never actual 95 * decode the filename. 96 */ 97 98 static void 99 dbs_replaceSlash(char *cp, int len) 100 { 101 while (len--) { 102 if (*cp == '/') 103 *cp = '-'; 104 cp++; 105 } 106 } 107 108 /* 109 * create a blob record from a key, data and return it in blobData. 110 * NOTE: The data element is static data (keeping with the dbm model). 111 */ 112 static void 113 dbs_mkBlob(DBS *dbsp, const DBT *key, const DBT *data, DBT *blobData) 114 { 115 unsigned char sha1_data[SHA1_LENGTH]; 116 char *b = dbsp->staticBlobArea; 117 PRUint32 length = data->size; 118 SECItem sha1Item; 119 120 b[0] = CERT_DB_FILE_VERSION; /* certdb version number */ 121 b[1] = (char)certDBEntryTypeBlob; /* type */ 122 b[2] = 0; /* flags */ 123 b[3] = 0; /* reserved */ 124 b[BLOB_LENGTH_START] = length & 0xff; 125 b[BLOB_LENGTH_START + 1] = (length >> 8) & 0xff; 126 b[BLOB_LENGTH_START + 2] = (length >> 16) & 0xff; 127 b[BLOB_LENGTH_START + 3] = (length >> 24) & 0xff; 128 sha1Item.data = sha1_data; 129 sha1Item.len = SHA1_LENGTH; 130 SHA1_HashBuf(sha1_data, key->data, key->size); 131 b[BLOB_NAME_START] = 'b'; /* Make sure we start with a alpha */ 132 NSSBase64_EncodeItem(NULL, &b[BLOB_NAME_START + 1], BLOB_NAME_LEN - 1, &sha1Item); 133 b[BLOB_BUF_LEN - 1] = 0; 134 dbs_replaceSlash(&b[BLOB_NAME_START + 1], BLOB_NAME_LEN - 1); 135 blobData->data = b; 136 blobData->size = BLOB_BUF_LEN; 137 return; 138 } 139 140 /* 141 * construct a path to the actual blob. The string returned must be 142 * freed by the caller with PR_smprintf_free. 143 * 144 * Note: this file does lots of consistancy checks on the DBT. The 145 * routines that call this depend on these checks, so they don't worry 146 * about them (success of this routine implies a good blobdata record). 147 */ 148 static char * 149 dbs_getBlobFilePath(char *blobdir, DBT *blobData) 150 { 151 const char *name; 152 153 if (blobdir == NULL) { 154 PR_SetError(SEC_ERROR_BAD_DATABASE, 0); 155 return NULL; 156 } 157 if (!dbs_IsBlob(blobData)) { 158 PR_SetError(SEC_ERROR_BAD_DATABASE, 0); 159 return NULL; 160 } 161 name = dbs_getBlobFileName(blobData); 162 if (!name || *name == 0) { 163 PR_SetError(SEC_ERROR_BAD_DATABASE, 0); 164 return NULL; 165 } 166 return PR_smprintf("%s" PATH_SEPARATOR "%s", blobdir, name); 167 } 168 169 /* 170 * Delete a blob file pointed to by the blob record. 171 */ 172 static void 173 dbs_removeBlob(DBS *dbsp, DBT *blobData) 174 { 175 char *file; 176 177 file = dbs_getBlobFilePath(dbsp->blobdir, blobData); 178 if (!file) { 179 return; 180 } 181 PR_Delete(file); 182 PR_smprintf_free(file); 183 } 184 185 /* 186 * Directory modes are slightly different, the 'x' bit needs to be on to 187 * access them. Copy all the read bits to 'x' bits 188 */ 189 static int 190 dbs_DirMode(int mode) 191 { 192 int x_bits = (mode >> 2) & 0111; 193 return mode | x_bits; 194 } 195 196 /* 197 * write a data blob to it's file. blobdData is the blob record that will be 198 * stored in the database. data is the actual data to go out on disk. 199 */ 200 static int 201 dbs_writeBlob(DBS *dbsp, int mode, DBT *blobData, const DBT *data) 202 { 203 char *file = NULL; 204 PRFileDesc *filed; 205 PRStatus status; 206 int len; 207 int error = 0; 208 209 file = dbs_getBlobFilePath(dbsp->blobdir, blobData); 210 if (!file) { 211 goto loser; 212 } 213 if (PR_Access(dbsp->blobdir, PR_ACCESS_EXISTS) != PR_SUCCESS) { 214 status = PR_MkDir(dbsp->blobdir, dbs_DirMode(mode)); 215 if (status != PR_SUCCESS) { 216 goto loser; 217 } 218 } 219 filed = PR_OpenFile(file, PR_CREATE_FILE | PR_TRUNCATE | PR_WRONLY, mode); 220 if (filed == NULL) { 221 error = PR_GetError(); 222 goto loser; 223 } 224 len = PR_Write(filed, data->data, data->size); 225 error = PR_GetError(); 226 PR_Close(filed); 227 if (len < (int)data->size) { 228 goto loser; 229 } 230 PR_smprintf_free(file); 231 return 0; 232 233 loser: 234 if (file) { 235 PR_Delete(file); 236 PR_smprintf_free(file); 237 } 238 /* don't let close or delete reset the error */ 239 PR_SetError(error, 0); 240 return -1; 241 } 242 243 /* 244 * platforms that cannot map the file need to read it into a temp buffer. 245 */ 246 static unsigned char * 247 dbs_EmulateMap(PRFileDesc *filed, int len) 248 { 249 unsigned char *addr; 250 PRInt32 dataRead; 251 252 addr = PORT_Alloc(len); 253 if (addr == NULL) { 254 return NULL; 255 } 256 257 dataRead = PR_Read(filed, addr, len); 258 if (dataRead != len) { 259 PORT_Free(addr); 260 if (dataRead > 0) { 261 /* PR_Read didn't set an error, we need to */ 262 PR_SetError(SEC_ERROR_BAD_DATABASE, 0); 263 } 264 return NULL; 265 } 266 267 return addr; 268 } 269 270 /* 271 * pull a database record off the disk 272 * data points to the blob record on input and the real record (if we could 273 * read it) on output. if there is an error data is not modified. 274 */ 275 static int 276 dbs_readBlob(DBS *dbsp, DBT *data) 277 { 278 char *file = NULL; 279 PRFileDesc *filed = NULL; 280 unsigned char *addr = NULL; 281 int error; 282 int len = -1; 283 284 file = dbs_getBlobFilePath(dbsp->blobdir, data); 285 if (!file) { 286 goto loser; 287 } 288 filed = PR_OpenFile(file, PR_RDONLY, 0); 289 PR_smprintf_free(file); 290 file = NULL; 291 if (filed == NULL) { 292 goto loser; 293 } 294 295 len = dbs_getBlobSize(data); 296 /* Bug 1323150 297 * PR_MemMap fails on Windows for larger certificates. 298 * https://msdn.microsoft.com/en-us/library/windows/desktop/aa366761(v=vs.85).aspx 299 * Let's always use the emulated map, i.e. read the file. 300 */ 301 addr = dbs_EmulateMap(filed, len); 302 if (addr == NULL) { 303 goto loser; 304 } 305 PR_Close(filed); 306 307 data->data = addr; 308 data->size = len; 309 return 0; 310 311 loser: 312 /* preserve the error code */ 313 error = PR_GetError(); 314 if (filed) { 315 PR_Close(filed); 316 } 317 PR_SetError(error, 0); 318 return -1; 319 } 320 321 /* 322 * actual DBM shims 323 */ 324 static int 325 dbs_get(const DB *dbs, const DBT *key, DBT *data, unsigned int flags) 326 { 327 int ret; 328 DBS *dbsp = (DBS *)dbs; 329 DB *db = (DB *)dbs->internal; 330 331 ret = (*db->get)(db, key, data, flags); 332 if ((ret == 0) && dbs_IsBlob(data)) { 333 ret = dbs_readBlob(dbsp, data); 334 } 335 336 return (ret); 337 } 338 339 static int 340 dbs_put(const DB *dbs, DBT *key, const DBT *data, unsigned int flags) 341 { 342 DBT blob; 343 int ret = 0; 344 DBS *dbsp = (DBS *)dbs; 345 DB *db = (DB *)dbs->internal; 346 347 /* If the db is readonly, just pass the data down to rdb and let it fail */ 348 if (!dbsp->readOnly) { 349 DBT oldData; 350 int ret1; 351 352 /* make sure the current record is deleted if it's a blob */ 353 ret1 = (*db->get)(db, key, &oldData, 0); 354 if ((ret1 == 0) && flags == R_NOOVERWRITE) { 355 /* let DBM return the error to maintain consistancy */ 356 return (*db->put)(db, key, data, flags); 357 } 358 if ((ret1 == 0) && dbs_IsBlob(&oldData)) { 359 dbs_removeBlob(dbsp, &oldData); 360 } 361 362 if (data->size > DBS_MAX_ENTRY_SIZE) { 363 dbs_mkBlob(dbsp, key, data, &blob); 364 ret = dbs_writeBlob(dbsp, dbsp->mode, &blob, data); 365 data = &blob; 366 } 367 } 368 369 if (ret == 0) { 370 ret = (*db->put)(db, key, data, flags); 371 } 372 return (ret); 373 } 374 375 static int 376 dbs_sync(const DB *dbs, unsigned int flags) 377 { 378 DB *db = (DB *)dbs->internal; 379 return (*db->sync)(db, flags); 380 } 381 382 static int 383 dbs_del(const DB *dbs, const DBT *key, unsigned int flags) 384 { 385 int ret; 386 DBS *dbsp = (DBS *)dbs; 387 DB *db = (DB *)dbs->internal; 388 389 if (!dbsp->readOnly) { 390 DBT oldData; 391 ret = (*db->get)(db, key, &oldData, 0); 392 if ((ret == 0) && dbs_IsBlob(&oldData)) { 393 dbs_removeBlob(dbsp, &oldData); 394 } 395 } 396 397 return (*db->del)(db, key, flags); 398 } 399 400 static int 401 dbs_seq(const DB *dbs, DBT *key, DBT *data, unsigned int flags) 402 { 403 int ret; 404 DBS *dbsp = (DBS *)dbs; 405 DB *db = (DB *)dbs->internal; 406 407 ret = (*db->seq)(db, key, data, flags); 408 if ((ret == 0) && dbs_IsBlob(data)) { 409 /* don't return a blob read as an error so traversals keep going */ 410 (void)dbs_readBlob(dbsp, data); 411 } 412 413 return (ret); 414 } 415 416 static int 417 dbs_close(DB *dbs) 418 { 419 DBS *dbsp = (DBS *)dbs; 420 DB *db = (DB *)dbs->internal; 421 int ret; 422 423 ret = (*db->close)(db); 424 PORT_Free(dbsp->blobdir); 425 PORT_Free(dbsp); 426 return ret; 427 } 428 429 static int 430 dbs_fd(const DB *dbs) 431 { 432 DB *db = (DB *)dbs->internal; 433 434 return (*db->fd)(db); 435 } 436 437 /* 438 * the naming convention we use is 439 * change the .xxx into .dir. (for nss it's always .db); 440 * if no .extension exists or is equal to .dir, add a .dir 441 * the returned data must be freed. 442 */ 443 #define DIRSUFFIX ".dir" 444 static char * 445 dbs_mkBlobDirName(const char *dbname) 446 { 447 int dbname_len = PORT_Strlen(dbname); 448 int dbname_end = dbname_len; 449 const char *cp; 450 char *blobDir = NULL; 451 452 /* scan back from the end looking for either a directory separator, a '.', 453 * or the end of the string. NOTE: Windows should check for both separators 454 * here. For now this is safe because we know NSS always uses a '.' 455 */ 456 for (cp = &dbname[dbname_len]; 457 (cp > dbname) && (*cp != '.') && (*cp != *PATH_SEPARATOR); 458 cp--) 459 /* Empty */; 460 if (*cp == '.') { 461 dbname_end = cp - dbname; 462 if (PORT_Strcmp(cp, DIRSUFFIX) == 0) { 463 dbname_end = dbname_len; 464 } 465 } 466 blobDir = PORT_ZAlloc(dbname_end + sizeof(DIRSUFFIX)); 467 if (blobDir == NULL) { 468 return NULL; 469 } 470 PORT_Memcpy(blobDir, dbname, dbname_end); 471 PORT_Memcpy(&blobDir[dbname_end], DIRSUFFIX, sizeof(DIRSUFFIX)); 472 return blobDir; 473 } 474 475 #define DBM_DEFAULT 0 476 static const HASHINFO dbs_hashInfo = { 477 DBS_BLOCK_SIZE, /* bucket size, must be greater than = to 478 * or maximum entry size (+ header) 479 * we allow before blobing */ 480 DBM_DEFAULT, /* Fill Factor */ 481 DBM_DEFAULT, /* number of elements */ 482 DBS_CACHE_SIZE, /* cache size */ 483 DBM_DEFAULT, /* hash function */ 484 DBM_DEFAULT, /* byte order */ 485 }; 486 487 /* 488 * the open function. NOTE: this is the only exposed function in this file. 489 * everything else is called through the function table pointer. 490 */ 491 DB * 492 dbsopen(const char *dbname, int flags, int mode, DBTYPE type, 493 const void *userData) 494 { 495 DB *db = NULL, *dbs = NULL; 496 DBS *dbsp = NULL; 497 498 /* NOTE: we are overriding userData with dbs_hashInfo. since all known 499 * callers pass 0, this is ok, otherwise we should merge the two */ 500 501 dbsp = (DBS *)PORT_ZAlloc(sizeof(DBS)); 502 if (!dbsp) { 503 return NULL; 504 } 505 dbs = &dbsp->db; 506 507 dbsp->blobdir = dbs_mkBlobDirName(dbname); 508 if (dbsp->blobdir == NULL) { 509 goto loser; 510 } 511 dbsp->mode = mode; 512 dbsp->readOnly = (PRBool)(flags == NO_RDONLY); 513 514 /* the real dbm call */ 515 db = dbopen(dbname, flags, mode, type, &dbs_hashInfo); 516 if (db == NULL) { 517 goto loser; 518 } 519 dbs->internal = (void *)db; 520 dbs->type = type; 521 dbs->close = dbs_close; 522 dbs->get = dbs_get; 523 dbs->del = dbs_del; 524 dbs->put = dbs_put; 525 dbs->seq = dbs_seq; 526 dbs->sync = dbs_sync; 527 dbs->fd = dbs_fd; 528 529 return dbs; 530 loser: 531 if (db) { 532 (*db->close)(db); 533 } 534 if (dbsp->blobdir) { 535 PORT_Free(dbsp->blobdir); 536 } 537 PORT_Free(dbsp); 538 return NULL; 539 }