security/nss/lib/softoken/legacydb/dbmshim.c

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

     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/. */
     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"
    14 #include "lgdb.h"
    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
    42 /* a Shim data structure. This data structure has a db built into it. */
    43 typedef struct DBSStr DBS;
    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 };
    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 }
    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;
    80     return &addr[BLOB_NAME_START];
    81 }
    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;
    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 }
    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  */
   104 static void
   105 dbs_replaceSlash(char *cp, int len)
   106 {
   107    while (len--) {
   108 	if (*cp == '/') *cp = '-';
   109 	cp++;
   110    }
   111 }
   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;
   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 }
   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;
   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 }
   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;
   183     file = dbs_getBlobFilePath(dbsp->blobdir, blobData);
   184     if (!file) {
   185 	return;
   186     }
   187     PR_Delete(file);
   188     PR_smprintf_free(file);
   189 }
   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 }
   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;
   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;
   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 }
   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  */
   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 }
   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 }
   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;
   296     addr = PORT_Alloc(len);
   297     if (addr == NULL) {
   298 	return NULL;
   299     }
   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     }
   311     return addr;
   312 }
   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;
   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     }
   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);
   359     data->data = addr;
   360     data->size = len;
   361     return 0;
   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 }
   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;
   387     dbs_freemap(dbsp);
   389     ret = (* db->get)(db, key, data, flags);
   390     if ((ret == 0) && dbs_IsBlob(data)) {
   391 	ret = dbs_readBlob(dbsp,data);
   392     }
   394     return(ret);
   395 }
   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;
   405     dbs_freemap(dbsp);
   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;
   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 	}
   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     }
   429     if (ret == 0) {
   430 	ret = (* db->put)(db, key, data, flags);
   431     }
   432     return(ret);
   433 }
   435 static int
   436 dbs_sync(const DB *dbs, unsigned int flags)
   437 {
   438     DB *db = (DB *)dbs->internal;
   439     DBS *dbsp = (DBS *)dbs;
   441     dbs_freemap(dbsp);
   443     return (* db->sync)(db, flags);
   444 }
   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;
   453     dbs_freemap(dbsp);
   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     }
   463     return (* db->del)(db, key, flags);
   464 }
   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;
   473     dbs_freemap(dbsp);
   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     }
   481     return(ret);
   482 }
   484 static int
   485 dbs_close(DB *dbs)
   486 {
   487     DBS *dbsp = (DBS *)dbs;
   488     DB *db = (DB *)dbs->internal;
   489     int ret;
   491     dbs_freemap(dbsp);
   492     ret = (* db->close)(db);
   493     PORT_Free(dbsp->blobdir);
   494     PORT_Free(dbsp);
   495     return ret;
   496 }
   498 static int
   499 dbs_fd(const DB *dbs)
   500 {
   501     DB *db = (DB *)dbs->internal;
   503     return (* db->fd)(db);
   504 }
   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;
   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 }
   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 };
   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;
   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 */
   570     dbsp = (DBS *)PORT_ZAlloc(sizeof(DBS));
   571     if (!dbsp) {
   572 	return NULL;
   573     }
   574     dbs = &dbsp->db;
   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;
   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;
   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 }

mercurial