Back to index

lightning-sunbird  0.9+nobinonly
dbmshim.c
Go to the documentation of this file.
00001 /* ***** BEGIN LICENSE BLOCK *****
00002  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00003  *
00004  * The contents of this file are subject to the Mozilla Public License Version
00005  * 1.1 (the "License"); you may not use this file except in compliance with
00006  * the License. You may obtain a copy of the License at
00007  * http://www.mozilla.org/MPL/
00008  *
00009  * Software distributed under the License is distributed on an "AS IS" basis,
00010  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00011  * for the specific language governing rights and limitations under the
00012  * License.
00013  *
00014  * The Original Code is the Netscape security libraries.
00015  *
00016  * The Initial Developer of the Original Code is
00017  * Netscape Communications Corporation.
00018  * Portions created by the Initial Developer are Copyright (C) 1994-2000
00019  * the Initial Developer. All Rights Reserved.
00020  *
00021  * Contributor(s):
00022  *
00023  * Alternatively, the contents of this file may be used under the terms of
00024  * either the GNU General Public License Version 2 or later (the "GPL"), or
00025  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00026  * in which case the provisions of the GPL or the LGPL are applicable instead
00027  * of those above. If you wish to allow use of your version of this file only
00028  * under the terms of either the GPL or the LGPL, and not to allow others to
00029  * use your version of this file under the terms of the MPL, indicate your
00030  * decision by deleting the provisions above and replace them with the notice
00031  * and other provisions required by the GPL or the LGPL. If you do not delete
00032  * the provisions above, a recipient may use your version of this file under
00033  * the terms of any one of the MPL, the GPL or the LGPL.
00034  *
00035  * ***** END LICENSE BLOCK ***** */
00036 
00037 /*
00038  * Berkeley DB 1.85 Shim code to handle blobs.
00039  *
00040  * $Id: dbmshim.c,v 1.11.30.1 2006/05/12 23:59:32 nelson%bolyard.com Exp $
00041  */
00042 #include "mcom_db.h"
00043 #include "secitem.h"
00044 #include "secder.h"
00045 #include "prprf.h"
00046 #include "cdbhdl.h"
00047 
00048 /* Call to SFTK_FreeSlot below */
00049 
00050 #include "pcertt.h"
00051 #include "secasn1.h"
00052 #include "secerr.h"
00053 #include "nssb64.h"
00054 #include "blapi.h"
00055 #include "sechash.h"
00056 
00057 #include "pkcs11i.h"
00058 
00059 /*
00060  *   Blob block:
00061  *   Byte 0   CERTDB Version           -+                       -+
00062  *   Byte 1   certDBEntryTypeBlob       |  BLOB_HEAD_LEN         |
00063  *   Byte 2   flags (always '0');       |                        |
00064  *   Byte 3   reserved (always '0');   -+                        |
00065  *   Byte 4   LSB length                | <--BLOB_LENGTH_START   | BLOB_BUF_LEN
00066  *   Byte 5       .                     |                        |
00067  *   Byte 6       .                     | BLOB_LENGTH_LEN        |
00068  *   Byte 7   MSB length                |                        |
00069  *   Byte 8   blob_filename   -+       -+  <-- BLOB_NAME_START   |
00070  *   Byte 9       .            | BLOB_NAME_LEN                   |
00071  *     .          .            |                                 |
00072  *   Byte 37      .           -+                                -+
00073  */
00074 #define DBS_BLOCK_SIZE (16*1024) /* 16 k */
00075 #define DBS_MAX_ENTRY_SIZE (DBS_BLOCK_SIZE - (2048)) /* 14 k */
00076 #define DBS_CACHE_SIZE      DBS_BLOCK_SIZE*8
00077 #define ROUNDDIV(x,y) (x+(y-1))/y
00078 #define BLOB_HEAD_LEN 4
00079 #define BLOB_LENGTH_START BLOB_HEAD_LEN
00080 #define BLOB_LENGTH_LEN 4
00081 #define BLOB_NAME_START BLOB_LENGTH_START+BLOB_LENGTH_LEN
00082 #define BLOB_NAME_LEN 1+ROUNDDIV(SHA1_LENGTH,3)*4+1
00083 #define BLOB_BUF_LEN BLOB_HEAD_LEN+BLOB_LENGTH_LEN+BLOB_NAME_LEN
00084 
00085 /* a Shim data structure. This data structure has a db built into it. */
00086 typedef struct DBSStr DBS;
00087 
00088 struct DBSStr {
00089     DB db;
00090     char *blobdir;
00091     int mode;
00092     PRBool readOnly;
00093     PRFileMap *dbs_mapfile;
00094     unsigned char *dbs_addr;
00095     PRUint32 dbs_len;
00096     char staticBlobArea[BLOB_BUF_LEN];
00097 };
00098     
00099     
00100 
00101 /*
00102  * return true if the Datablock contains a blobtype
00103  */
00104 static PRBool
00105 dbs_IsBlob(DBT *blobData)
00106 {
00107     unsigned char *addr = (unsigned char *)blobData->data;
00108     if (blobData->size < BLOB_BUF_LEN) {
00109        return PR_FALSE;
00110     }
00111     return addr && ((certDBEntryType) addr[1] == certDBEntryTypeBlob);
00112 }
00113 
00114 /*
00115  * extract the filename in the blob of the real data set.
00116  * This value is not malloced (does not need to be freed by the caller.
00117  */
00118 static const char *
00119 dbs_getBlobFileName(DBT *blobData)
00120 {
00121     char *addr = (char *)blobData->data;
00122 
00123     return &addr[BLOB_NAME_START];
00124 }
00125 
00126 /*
00127  * extract the size of the actual blob from the blob record
00128  */
00129 static PRUint32
00130 dbs_getBlobSize(DBT *blobData)
00131 {
00132     unsigned char *addr = (unsigned char *)blobData->data;
00133 
00134     return (PRUint32)(addr[BLOB_LENGTH_START+3] << 24) | 
00135                      (addr[BLOB_LENGTH_START+2] << 16) | 
00136                      (addr[BLOB_LENGTH_START+1] << 8) | 
00137                      addr[BLOB_LENGTH_START];
00138 }
00139 
00140 
00141 /* We are using base64 data for the filename, but base64 data can include a
00142  * '/' which is interpreted as a path separator on  many platforms. Replace it
00143  * with an inocuous '-'. We don't need to convert back because we never actual
00144  * decode the filename.
00145  */
00146 
00147 static void
00148 dbs_replaceSlash(char *cp, int len)
00149 {
00150    while (len--) {
00151        if (*cp == '/') *cp = '-';
00152        cp++;
00153    }
00154 }
00155 
00156 /*
00157  * create a blob record from a key, data and return it in blobData.
00158  * NOTE: The data element is static data (keeping with the dbm model).
00159  */
00160 static void
00161 dbs_mkBlob(DBS *dbsp,const DBT *key, const DBT *data, DBT *blobData)
00162 {
00163    unsigned char sha1_data[SHA1_LENGTH];
00164    char *b = dbsp->staticBlobArea;
00165    PRUint32 length = data->size;
00166    SECItem sha1Item;
00167 
00168    b[0] = CERT_DB_FILE_VERSION; /* certdb version number */
00169    b[1] = (char) certDBEntryTypeBlob; /* type */
00170    b[2] = 0; /* flags */
00171    b[3] = 0; /* reserved */
00172    b[BLOB_LENGTH_START] = length & 0xff;
00173    b[BLOB_LENGTH_START+1] = (length >> 8) & 0xff;
00174    b[BLOB_LENGTH_START+2] = (length >> 16) & 0xff;
00175    b[BLOB_LENGTH_START+3] = (length >> 24) & 0xff;
00176    sha1Item.data = sha1_data;
00177    sha1Item.len = SHA1_LENGTH;
00178    SHA1_HashBuf(sha1_data,key->data,key->size);
00179    b[BLOB_NAME_START]='b'; /* Make sure we start with a alpha */
00180    NSSBase64_EncodeItem(NULL,&b[BLOB_NAME_START+1],BLOB_NAME_LEN-1,&sha1Item);
00181    b[BLOB_BUF_LEN-1] = 0;
00182    dbs_replaceSlash(&b[BLOB_NAME_START+1],BLOB_NAME_LEN-1);
00183    blobData->data = b;
00184    blobData->size = BLOB_BUF_LEN;
00185    return;
00186 }
00187    
00188 
00189 /*
00190  * construct a path to the actual blob. The string returned must be
00191  * freed by the caller with PR_smprintf_free.
00192  *
00193  * Note: this file does lots of consistancy checks on the DBT. The
00194  * routines that call this depend on these checks, so they don't worry
00195  * about them (success of this routine implies a good blobdata record).
00196  */ 
00197 static char *
00198 dbs_getBlobFilePath(char *blobdir,DBT *blobData)
00199 {
00200     const char *name;
00201 
00202     if (blobdir == NULL) {
00203        PR_SetError(SEC_ERROR_BAD_DATABASE,0);
00204        return NULL;
00205     }
00206     if (!dbs_IsBlob(blobData)) {
00207        PR_SetError(SEC_ERROR_BAD_DATABASE,0);
00208        return NULL;
00209     }
00210     name = dbs_getBlobFileName(blobData);
00211     if (!name || *name == 0) {
00212        PR_SetError(SEC_ERROR_BAD_DATABASE,0);
00213        return NULL;
00214     }
00215     return  PR_smprintf("%s" PATH_SEPARATOR "%s", blobdir, name);
00216 }
00217 
00218 /*
00219  * Delete a blob file pointed to by the blob record.
00220  */
00221 static void
00222 dbs_removeBlob(DBS *dbsp, DBT *blobData)
00223 {
00224     char *file;
00225 
00226     file = dbs_getBlobFilePath(dbsp->blobdir, blobData);
00227     if (!file) {
00228        return;
00229     }
00230     PR_Delete(file);
00231     PR_smprintf_free(file);
00232 }
00233 
00234 /*
00235  * Directory modes are slightly different, the 'x' bit needs to be on to
00236  * access them. Copy all the read bits to 'x' bits
00237  */
00238 static int
00239 dbs_DirMode(int mode)
00240 {
00241   int x_bits = (mode >> 2) &  0111;
00242   return mode | x_bits;
00243 }
00244 
00245 /*
00246  * write a data blob to it's file. blobdData is the blob record that will be
00247  * stored in the database. data is the actual data to go out on disk.
00248  */
00249 static int
00250 dbs_writeBlob(DBS *dbsp, int mode, DBT *blobData, const DBT *data)
00251 {
00252     char *file = NULL;
00253     PRFileDesc *filed;
00254     PRStatus status;
00255     int len;
00256     int error = 0;
00257 
00258     file = dbs_getBlobFilePath(dbsp->blobdir, blobData);
00259     if (!file) {
00260        goto loser;
00261     }
00262     if (PR_Access(dbsp->blobdir, PR_ACCESS_EXISTS) != PR_SUCCESS) {
00263        status = PR_MkDir(dbsp->blobdir,dbs_DirMode(mode));
00264        if (status != PR_SUCCESS) {
00265            goto loser;
00266        }
00267     }
00268     filed = PR_OpenFile(file,PR_CREATE_FILE|PR_TRUNCATE|PR_WRONLY, mode);
00269     if (filed == NULL) {
00270        error = PR_GetError();
00271        goto loser;
00272     }
00273     len = PR_Write(filed,data->data,data->size);
00274     error = PR_GetError();
00275     PR_Close(filed);
00276     if (len < (int)data->size) {
00277        goto loser;
00278     }
00279     PR_smprintf_free(file);
00280     return 0;
00281 
00282 loser:
00283     if (file) {
00284        PR_Delete(file);
00285        PR_smprintf_free(file);
00286     }
00287     /* don't let close or delete reset the error */
00288     PR_SetError(error,0);
00289     return -1;
00290 }
00291 
00292 
00293 /*
00294  * we need to keep a address map in memory between calls to DBM.
00295  * remember what we have mapped can close it when we get another dbm
00296  * call. 
00297  *
00298  * NOTE: Not all platforms support mapped files. This code is designed to
00299  * detect this at runtime. If map files aren't supported the OS will indicate
00300  * this by failing the PR_Memmap call. In this case we emulate mapped files
00301  * by just reading in the file into regular memory. We signal this state by
00302  * making dbs_mapfile NULL and dbs_addr non-NULL.
00303  */
00304 
00305 static void
00306 dbs_freemap(DBS *dbsp)
00307 {
00308     if (dbsp->dbs_mapfile) {
00309        PR_MemUnmap(dbsp->dbs_addr,dbsp->dbs_len);
00310        PR_CloseFileMap(dbsp->dbs_mapfile);
00311        dbsp->dbs_mapfile = NULL;
00312        dbsp->dbs_addr = NULL;
00313        dbsp->dbs_len = 0;
00314     } else if (dbsp->dbs_addr) {
00315        PORT_Free(dbsp->dbs_addr);
00316        dbsp->dbs_addr = NULL;
00317        dbsp->dbs_len = 0;
00318     }
00319     return;
00320 }
00321 
00322 static void
00323 dbs_setmap(DBS *dbsp, PRFileMap *mapfile, unsigned char *addr, PRUint32 len)
00324 {
00325     dbsp->dbs_mapfile = mapfile;
00326     dbsp->dbs_addr = addr;
00327     dbsp->dbs_len = len;
00328 }
00329 
00330 /*
00331  * platforms that cannot map the file need to read it into a temp buffer.
00332  */
00333 static unsigned char *
00334 dbs_EmulateMap(PRFileDesc *filed, int len)
00335 {
00336     unsigned char *addr;
00337     PRInt32 dataRead;
00338 
00339     addr = PORT_Alloc(len);
00340     if (addr == NULL) {
00341        return NULL;
00342     }
00343 
00344     dataRead = PR_Read(filed,addr,len);
00345     if (dataRead != len) {
00346        PORT_Free(addr);
00347        if (dataRead > 0) {
00348            /* PR_Read didn't set an error, we need to */
00349            PR_SetError(SEC_ERROR_BAD_DATABASE,0);
00350        }
00351        return NULL;
00352     }
00353 
00354     return addr;
00355 }
00356 
00357 
00358 /*
00359  * pull a database record off the disk
00360  * data points to the blob record on input and the real record (if we could
00361  * read it) on output. if there is an error data is not modified.
00362  */
00363 static int
00364 dbs_readBlob(DBS *dbsp, DBT *data)
00365 {
00366     char *file = NULL;
00367     PRFileDesc *filed = NULL;
00368     PRFileMap *mapfile = NULL;
00369     unsigned char *addr = NULL;
00370     int error;
00371     int len = -1;
00372 
00373     file = dbs_getBlobFilePath(dbsp->blobdir, data);
00374     if (!file) {
00375        goto loser;
00376     }
00377     filed = PR_OpenFile(file,PR_RDONLY,0);
00378     PR_smprintf_free(file); file = NULL;
00379     if (filed == NULL) {
00380        goto loser;
00381     }
00382 
00383     len = dbs_getBlobSize(data);
00384     mapfile = PR_CreateFileMap(filed, len, PR_PROT_READONLY);
00385     if (mapfile == NULL) {
00386         /* USE PR_GetError instead of PORT_GetError here
00387          * because we are getting the error from PR_xxx
00388          * function */
00389        if (PR_GetError() != PR_NOT_IMPLEMENTED_ERROR) {
00390            goto loser;
00391        }
00392        addr = dbs_EmulateMap(filed, len);
00393     } else {
00394        addr = PR_MemMap(mapfile, 0, len);
00395     }
00396     if (addr == NULL) {
00397        goto loser;
00398     }
00399     PR_Close(filed);
00400     dbs_setmap(dbsp,mapfile,addr,len);
00401 
00402     data->data = addr;
00403     data->size = len;
00404     return 0;
00405 
00406 loser:
00407     /* preserve the error code */
00408     error = PR_GetError();
00409     if (mapfile) {
00410        PR_CloseFileMap(mapfile);
00411     }
00412     if (filed) {
00413        PR_Close(filed);
00414     }
00415     PR_SetError(error,0);
00416     return -1;
00417 }
00418 
00419 /*
00420  * actual DBM shims
00421  */
00422 static int
00423 dbs_get(const DB *dbs, const DBT *key, DBT *data, unsigned int flags)
00424 {
00425     int ret;
00426     DBS *dbsp = (DBS *)dbs;
00427     DB *db = (DB *)dbs->internal;
00428     
00429 
00430     dbs_freemap(dbsp);
00431     
00432     ret = (* db->get)(db, key, data, flags);
00433     if ((ret == 0) && dbs_IsBlob(data)) {
00434        ret = dbs_readBlob(dbsp,data);
00435     }
00436 
00437     return(ret);
00438 }
00439 
00440 static int
00441 dbs_put(const DB *dbs, DBT *key, const DBT *data, unsigned int flags)
00442 {
00443     DBT blob;
00444     int ret = 0;
00445     DBS *dbsp = (DBS *)dbs;
00446     DB *db = (DB *)dbs->internal;
00447 
00448     dbs_freemap(dbsp);
00449 
00450     /* If the db is readonly, just pass the data down to rdb and let it fail */
00451     if (!dbsp->readOnly) {
00452        DBT oldData;
00453        int ret1;
00454 
00455        /* make sure the current record is deleted if it's a blob */
00456        ret1 = (*db->get)(db,key,&oldData,0);
00457         if ((ret1 == 0) && flags == R_NOOVERWRITE) {
00458            /* let DBM return the error to maintain consistancy */
00459            return (* db->put)(db, key, data, flags);
00460        }
00461        if ((ret1 == 0) && dbs_IsBlob(&oldData)) {
00462            dbs_removeBlob(dbsp, &oldData);
00463        }
00464 
00465        if (data->size > DBS_MAX_ENTRY_SIZE) {
00466            dbs_mkBlob(dbsp,key,data,&blob);
00467            ret = dbs_writeBlob(dbsp, dbsp->mode, &blob, data);
00468            data = &blob;
00469        }
00470     }
00471 
00472     if (ret == 0) {
00473        ret = (* db->put)(db, key, data, flags);
00474     }
00475     return(ret);
00476 }
00477 
00478 static int
00479 dbs_sync(const DB *dbs, unsigned int flags)
00480 {
00481     DB *db = (DB *)dbs->internal;
00482     DBS *dbsp = (DBS *)dbs;
00483 
00484     dbs_freemap(dbsp);
00485 
00486     return (* db->sync)(db, flags);
00487 }
00488 
00489 static int
00490 dbs_del(const DB *dbs, const DBT *key, unsigned int flags)
00491 {
00492     int ret;
00493     DBS *dbsp = (DBS *)dbs;
00494     DB *db = (DB *)dbs->internal;
00495 
00496     dbs_freemap(dbsp);
00497 
00498     if (!dbsp->readOnly) {
00499        DBT oldData;
00500        ret = (*db->get)(db,key,&oldData,0);
00501        if ((ret == 0) && dbs_IsBlob(&oldData)) {
00502            dbs_removeBlob(dbsp,&oldData);
00503        }
00504     }
00505 
00506     return (* db->del)(db, key, flags);
00507 }
00508 
00509 static int
00510 dbs_seq(const DB *dbs, DBT *key, DBT *data, unsigned int flags)
00511 {
00512     int ret;
00513     DBS *dbsp = (DBS *)dbs;
00514     DB *db = (DB *)dbs->internal;
00515     
00516     dbs_freemap(dbsp);
00517     
00518     ret = (* db->seq)(db, key, data, flags);
00519     if ((ret == 0) && dbs_IsBlob(data)) {
00520        /* don't return a blob read as an error so traversals keep going */
00521        (void) dbs_readBlob(dbsp,data);
00522     }
00523 
00524     return(ret);
00525 }
00526 
00527 static int
00528 dbs_close(DB *dbs)
00529 {
00530     DBS *dbsp = (DBS *)dbs;
00531     DB *db = (DB *)dbs->internal;
00532     int ret;
00533 
00534     dbs_freemap(dbsp);
00535     ret = (* db->close)(db);
00536     PORT_Free(dbsp->blobdir);
00537     PORT_Free(dbsp);
00538     return ret;
00539 }
00540 
00541 static int
00542 dbs_fd(const DB *dbs)
00543 {
00544     DB *db = (DB *)dbs->internal;
00545 
00546     return (* db->fd)(db);
00547 }
00548 
00549 /*
00550  * the naming convention we use is
00551  * change the .xxx into .dir. (for nss it's always .db);
00552  * if no .extension exists or is equal to .dir, add a .dir 
00553  * the returned data must be freed.
00554  */
00555 #define DIRSUFFIX ".dir"
00556 static char *
00557 dbs_mkBlobDirName(const char *dbname)
00558 {
00559     int dbname_len = PORT_Strlen(dbname);
00560     int dbname_end = dbname_len;
00561     const char *cp;
00562     char *blobDir = NULL;
00563 
00564     /* scan back from the end looking for either a directory separator, a '.',
00565      * or the end of the string. NOTE: Windows should check for both separators
00566      * here. For now this is safe because we know NSS always uses a '.'
00567      */
00568     for (cp = &dbname[dbname_len]; 
00569               (cp > dbname) && (*cp != '.') && (*cp != *PATH_SEPARATOR) ;
00570                      cp--)
00571        /* Empty */ ;
00572     if (*cp == '.') {
00573        dbname_end = cp - dbname;
00574        if (PORT_Strcmp(cp,DIRSUFFIX) == 0) {
00575            dbname_end = dbname_len;
00576        }
00577     }
00578     blobDir = PORT_ZAlloc(dbname_end+sizeof(DIRSUFFIX));
00579     if (blobDir == NULL) {
00580        return NULL;
00581     }
00582     PORT_Memcpy(blobDir,dbname,dbname_end);
00583     PORT_Memcpy(&blobDir[dbname_end],DIRSUFFIX,sizeof(DIRSUFFIX));
00584     return blobDir;
00585 }
00586 
00587 #define DBM_DEFAULT 0
00588 static const HASHINFO dbs_hashInfo = {
00589        DBS_BLOCK_SIZE,             /* bucket size, must be greater than = to
00590                              * or maximum entry size (+ header)
00591                              * we allow before blobing */
00592        DBM_DEFAULT,         /* Fill Factor */
00593        DBM_DEFAULT,         /* number of elements */
00594        DBS_CACHE_SIZE,             /* cache size */
00595        DBM_DEFAULT,         /* hash function */
00596        DBM_DEFAULT,         /* byte order */
00597 };
00598 
00599 /*
00600  * the open function. NOTE: this is the only exposed function in this file.
00601  * everything else is called through the function table pointer.
00602  */
00603 DB *
00604 dbsopen(const char *dbname, int flags, int mode, DBTYPE type,
00605                                                   const void *userData)
00606 {
00607     DB *db = NULL,*dbs = NULL;
00608     DBS *dbsp = NULL;
00609 
00610     /* NOTE: we are overriding userData with dbs_hashInfo. since all known
00611      * callers pass 0, this is ok, otherwise we should merge the two */
00612 
00613     dbsp = (DBS *)PORT_ZAlloc(sizeof(DBS));
00614     if (!dbsp) {
00615        return NULL;
00616     }
00617     dbs = &dbsp->db;
00618 
00619     dbsp->blobdir=dbs_mkBlobDirName(dbname);
00620     if (dbsp->blobdir == NULL) {
00621        goto loser;
00622     }
00623     dbsp->mode = mode;
00624     dbsp->readOnly = (PRBool)(flags == NO_RDONLY);
00625     dbsp->dbs_mapfile = NULL;
00626     dbsp->dbs_addr = NULL;
00627     dbsp->dbs_len = 0;
00628 
00629     /* the real dbm call */
00630     db = dbopen(dbname, flags, mode, type, &dbs_hashInfo);
00631     if (db == NULL) {
00632        goto loser;
00633     }
00634     dbs->internal = (void *) db;
00635     dbs->type = type;
00636     dbs->close = dbs_close;
00637     dbs->get = dbs_get;
00638     dbs->del = dbs_del;
00639     dbs->put = dbs_put;
00640     dbs->seq = dbs_seq;
00641     dbs->sync = dbs_sync;
00642     dbs->fd = dbs_fd;
00643 
00644     return dbs;
00645 loser:
00646     if (db) {
00647        (*db->close)(db);
00648     }
00649     if (dbsp && dbsp->blobdir) {
00650        PORT_Free(dbsp->blobdir);
00651     }
00652     if (dbsp) {
00653        PORT_Free(dbsp);
00654     }
00655     return NULL;
00656 }