diff --git a/.gitattributes b/.gitattributes
index 3d754c384fb8537f46de15781c2f2ae134dfdbcb..1092c83d386feb726e524679c5e2108b93e4286e 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -36,6 +36,7 @@ testfont binary
 /lib/modules/LR.pmod/rule.pike foreign_ident
 /lib/modules/LR.pmod/scanner.pike foreign_ident
 /lib/modules/MIME.pmod foreign_ident
+/lib/modules/PDB.pmod foreign_ident
 /lib/modules/SSL.pmod/alert.pike foreign_ident
 /lib/modules/SSL.pmod/cipher.pike foreign_ident
 /lib/modules/SSL.pmod/connection.pike foreign_ident
diff --git a/lib/modules/PDB.pmod b/lib/modules/PDB.pmod
new file mode 100644
index 0000000000000000000000000000000000000000..2910bd1861971b0e83c5deef6a7fd78fca83d394
--- /dev/null
+++ b/lib/modules/PDB.pmod
@@ -0,0 +1,638 @@
+/*
+ * $Id: PDB.pmod,v 1.1 1998/03/23 17:45:29 noring Exp $
+ */
+
+#if constant(thread_create)
+#define THREAD_SAFE
+#define LOCK() do { object key; catch(key=lock())
+#define UNLOCK() key=0; } while(0)
+#else
+#undef THREAD_SAFE
+#define LOCK() do {
+#define UNLOCK() } while(0)
+#endif
+
+#define PDB_ERR(msg) (exceptions?throw(({ "(PDB) "+msg+"\n",backtrace() })):0)
+#define PDB_WARN(msg) werror("(PDB Warning) "+msg+"\n")
+
+class FileIO {
+
+#ifdef THREAD_SAFE
+  static inherit Thread.Mutex;
+#endif
+
+  static int exceptions, sov;
+
+  static private object open(string f, string m)
+  {
+    object o = Stdio.File();
+    if(!o->open(f,m))
+      return PDB_ERR("Failed to open file. "+f+": "+strerror(o->errno()));
+    return o;
+  }
+  
+  static int safe_write(object o, string d, string ctx)
+  {
+    int warned_already=0;
+    int n;
+    for(;;) {
+      n = o->write(d);
+      if(!sov || n == sizeof(d) || n<0)
+	break;
+      d = d[n..];
+      if(!warned_already) {
+	PDB_WARN(ctx+": Disk seems to be full.");
+	warned_already=1;
+      }
+      sleep(1);
+    }
+    return (n<0? n : (n==strlen(d)? 1 : 0));
+  }
+
+  static int write_file(string f, mixed d)
+  {
+    d = encode_value(d);
+    catch {
+      string q;
+      object g;
+      if (sizeof(indices(g=Gz))) {
+	if(strlen(q=g->deflate()->deflate(d)) < strlen(d))
+	  d=q;
+      }
+    };
+    object o = open(f+".tmp","wct");
+    int n = safe_write(o, d, "write_file("+f+")");
+    o->close();
+    if(n == 1)
+      return mv(f+".tmp", f)?1:PDB_ERR("Cannot move file.");
+    rm(f+".tmp");
+    return PDB_ERR("Failed to write file. Disk full?");
+  }
+  
+  static mixed read_file(string f)
+  {
+    object o = open(f,"r");
+    string d = o->read();
+    catch {
+      object g;
+      if (sizeof(indices(g=Gz))) {
+	d=g->inflate()->inflate(d);
+      }
+    };
+    return decode_value(d);
+  }
+}
+
+
+class Bucket
+{
+  inherit FileIO;
+  static object file=Stdio.File();
+  static array free_blocks = ({});
+  static string rf;
+  static int last_block, dirty;
+  static function db_log;
+  int size;
+
+  static int log(int subtype, mixed arg)
+  {
+    return db_log('B', subtype, size, arg);
+  }
+
+  static int write_at(int offset, string to)
+  {
+    if(file->seek(offset*size) < 0)
+      return PDB_ERR("write_at: seek failed");
+
+    if(safe_write(file, to, "write_at") == 1)
+      return 1;
+
+    return PDB_ERR("Failed to write file. Disk full?");
+  }
+  
+  static string read_at(int offset)
+  {
+    if(file->seek(offset*size) == -1)
+      return PDB_ERR("Failed to seek.");
+    return file->read(size);
+  }
+  
+  mixed get_entry(int offset)
+  {
+    LOCK();
+    return read_at(offset);
+    UNLOCK();
+  }
+  
+  static void save_free_blocks()
+  {
+    if(write_file(rf+".free", ({last_block, free_blocks})))
+      dirty = 0;
+  }
+  
+  void free_entry(int offset)
+  {
+    LOCK();
+    free_blocks += ({ offset });
+    dirty = 1;
+    log('F', offset);
+    if(size<4)   write_at(offset,"F");
+    else         write_at(offset,"FREE");
+    UNLOCK();
+  }
+
+  int allocate_entry()
+  {
+    int b;
+    LOCK();
+    if(sizeof(free_blocks)) {
+      b = free_blocks[0];
+      free_blocks -= ({ b });
+    } else
+      b = last_block++;
+    dirty = 1;
+    if(!log('A', b))
+      return -1;
+    UNLOCK();
+    return b;
+  }
+
+  int sync()
+  {
+    LOCK();
+    if(dirty)
+      save_free_blocks();
+    return !dirty;
+    UNLOCK();
+  }
+
+  int set_entry(int offset, string to)
+  {
+    if(strlen(to) > size) return 0;
+    LOCK();
+    return write_at(offset, to);
+    UNLOCK();
+  }
+
+  void restore_from_log(array log)
+  {
+    multiset alloced = (< >);
+    multiset freed = (< >);
+    foreach(log, array entry)
+      switch(entry[0]) {
+      case 'A':
+	alloced[entry[1]] = 1;
+	freed[entry[1]] = 0;
+	break;
+      case 'F':
+	freed[entry[1]] = 1;
+	alloced[entry[1]] = 0;
+	break;
+      }
+    foreach(sort(indices(alloced)), int a)
+      if(a>=last_block) {
+	int i;
+	for(i=last_block; i<a; i++)
+	  free_blocks += ({ i });
+	last_block = a+1;
+	dirty = 1;
+      } else if(search(free_blocks, a)>=0) {
+	free_blocks -= ({ a });
+	dirty = 1;
+      }
+    array(int) fr = indices(freed) - free_blocks;
+    if(sizeof(fr)) {
+      free_blocks += fr;
+      foreach(fr, int f)
+	if(size<4)   write_at(f,"F");
+	else         write_at(f,"FREE");
+      dirty = 1;
+    }
+  }
+  
+  void create(string d, int ms, int write, function logfun, int exceptions_in,
+	      int sov_in)
+  {
+    string mode="r";
+    size=ms;
+    rf = d+ms;
+    db_log = logfun;
+    exceptions = exceptions_in;
+    sov = sov_in;
+    if(write) { mode="rwc"; }
+    catch {
+      array t = read_file(rf+".free");
+      last_block = t[0];
+      free_blocks = t[1];
+    };
+    if(!file->open(rf,mode)) destruct();
+  }
+
+  void destroy()
+  {
+    sync();
+    if(file)
+      destruct(file);
+  }
+};
+
+
+class Table
+{
+  inherit FileIO;
+  static mapping index = ([ ]);
+  static int compress, write;
+  static string dir, name;
+  static function get_bucket;
+  static int dirty;
+  static function db_log;
+
+  static int log(int subtype, mixed ... arg)
+  {
+    return db_log('T', subtype, name, @arg);
+  }
+
+  int sync()
+  {
+    LOCK();
+    if(dirty) {
+      if(write_file(dir+".INDEX", index))
+	dirty = 0;
+    }
+    return !dirty;
+    UNLOCK();
+  }
+
+  int find_nearest_2x(int num)
+  {
+    for(int b=4;b<32;b++) if((1<<b) >= num) return (1<<b);
+  }
+
+  function scheme = find_nearest_2x;
+
+
+  void delete(string in)
+  {
+    if(!write) return;
+    LOCK();
+    array i;
+    if(i=index[in]) {
+      m_delete(index,in);
+      dirty = 1;
+      log('D', in);
+      object bucket = get_bucket(i[0]);
+      bucket->free_entry(i[1]);
+    }
+    UNLOCK();
+  }
+
+  mixed set(string in, mixed to)
+  {
+    if(!write) return 0;
+    string ts = encode_value(to);
+    if(compress)
+      catch {
+	string q;
+	object g;
+	if (sizeof(indices(g=Gz))) {
+	  if(strlen(q=g->deflate()->deflate(ts)) < strlen(ts))
+	    ts=q;
+	}
+      };
+    LOCK();
+    object bucket = get_bucket(scheme(strlen(ts)));
+    int of = bucket->allocate_entry();
+    if(of>=0 && bucket->set_entry(of, ts)) {
+      array new_entry = ({ bucket->size, of });
+      if(log('C', in, new_entry)) {
+	delete(in);
+	index[in]=new_entry;
+	dirty = 1;
+      } else
+	to = 0;
+    } else
+      to = 0;
+    UNLOCK();
+    return to;
+  }
+
+  mixed `[]=(string in,mixed to) {
+    return set(in,to);
+  }
+  
+  mixed get(string in)
+  {
+    array i;
+    mixed d;
+    LOCK();
+    if(!(i=index[in])) return 0;
+    object bucket = get_bucket(i[0]);
+    d = bucket->get_entry(i[1]);
+    UNLOCK();
+    catch {
+      object g;
+      if (sizeof(indices(g=Gz))) {
+	d=g->inflate()->inflate(d);
+      }
+    };
+    return decode_value( d );
+  }
+
+  mixed `[](string in) {
+    return get(in);
+  }
+
+  array list_keys() {
+//  LOCK();
+    return indices(index);
+//  UNLOCK();
+  }
+  
+  array _indices() {
+    return list_keys();
+  }
+
+  array match(string match)
+  {
+    return glob(match, _indices());
+  }
+
+  void purge()
+  {
+    // remove table...
+    if(!write) return;
+    LOCK();
+    foreach(_indices(), string d)
+      delete(d);
+    rm(dir+".INDEX");
+    index=([]);
+    dirty = 0;
+    log('P');
+    UNLOCK();
+  }
+
+  void rehash()
+  {
+    // TBD ...
+  }
+  
+  void restore_from_log(array log)
+  {
+    int purge = 0;
+    foreach(log, array entry)
+      switch(entry[0]) {
+      case 'D':
+	m_delete(index, entry[1]);
+	break;
+      case 'C':
+	index[entry[1]] = entry[2];
+	break;
+      case 'P':
+	index = ([]);
+	purge = 1;
+	break;
+      }
+    if(!write) return;
+    if(purge) {
+      rm(dir+".INDEX");
+      dirty = sizeof(index)>0;
+    } else
+      dirty = sizeof(log)>0;
+  }
+
+  void create(string n, string d, int wp, int cp, function fn, function logfun, int exceptions_in, int sov_in)
+  {
+    name = n;
+    get_bucket = fn;
+    db_log = logfun;
+    dir = d;
+    exceptions = exceptions_in;
+    sov = sov_in;
+    if(sizeof(predef::indices(Gz)) && cp) compress=1;
+    if(wp) write=1;
+    catch { index = read_file(dir+".INDEX"); };
+  }
+
+  void destroy()
+  {
+    sync();
+  }
+};
+
+
+class db
+{
+  inherit FileIO;
+#ifdef THREAD_SAFE
+  static inherit Thread.Mutex;
+#endif
+
+  static int write, compress;
+  static string dir;
+  static mapping (int:object(Bucket)) buckets = ([]);
+  static mapping (string:object(Table)) tables = ([]);
+  static object(Stdio.File) logfile;
+
+  static int log(int major, int minor, mixed ... args)
+  {
+    LOCK();
+    if(logfile) {
+      int rc;
+      string entry = sprintf("%c%c%s\n", major, minor,
+			     replace(encode_value(args),
+				     ({ "\203", "\n" }),
+				     ({ "\203\203", "\203n" })));
+
+      if((rc = safe_write(logfile, entry, "log"))!=1)
+	return PDB_ERR("Failed to write log: "+(rc<0? strerror(logfile->errno()):
+						"Disk full (probably)"));
+    }
+    UNLOCK();
+    return 1;
+  }
+
+  static object(Bucket) get_bucket(int s)
+  {
+    object bucket;
+    LOCK();
+    if(!(bucket = buckets[s]))
+      buckets[s] = bucket = Bucket( dir+"Buckets/", s, write, log, exceptions, sov );
+    UNLOCK();
+    return bucket;
+  }
+  
+  static void mkdirhier(string from)
+  {
+    string a, b;
+    array f;
+
+    f=(from/"/");
+    b="";
+
+    foreach(f[0..sizeof(f)-2], a)
+    {
+      mkdir(b+a);
+      b+=a+"/";
+    }
+  }
+
+  object(Table) table(string tname)
+  {
+    tname -= "/";
+    LOCK();
+    if(tables[tname])
+      return tables[tname];
+    return tables[tname] =
+      Table(tname, combine_path(dir, tname), write, compress, get_bucket, log, exceptions, sov );
+    UNLOCK();
+  }
+
+  object(Table) `[](string t)
+  {
+    return table(t);
+  }
+  
+  array(string) list_tables()
+  {
+    LOCK();
+    return Array.map(glob("*.INDEX", get_dir(dir) || ({})),
+		     lambda(string s)
+		     { return s[..(sizeof(s)-1-sizeof(".INDEX"))]; });
+    UNLOCK();
+  }
+
+  array(string) _indices()
+  {
+    return list_tables();
+  }
+
+  static void rotate_logs()
+  {
+    mv(dir+"log.1", dir+"log.2");
+    logfile->open(dir+"log.1", "cwta");
+  }
+
+  int sync()
+  {
+    array b, t;
+    int rc, ok=1;
+    LOCK();
+    rc = log('D', '1');
+    b = values(buckets);
+    t = values(tables);
+    UNLOCK();
+    if(rc) {
+      foreach(b+t, object o)
+	if(catch{if(!o->sync()) ok=0;}) ok=0;
+      if(ok) {
+	LOCK();
+	if(log('D', '2'))
+	  rotate_logs();
+	else ok=0;
+	UNLOCK();
+      }
+    } else ok=0;
+    remove_call_out(sync);
+    call_out(sync, 200);
+    return ok;
+  }
+
+  // Remove maximum one level of directories and files
+  static void level2_rm(string f)
+  {
+    if(sizeof(f) > 1 && f[-1] == '/')
+      f = f[0..sizeof(f)-2];  // remove /'s
+    if((file_stat(f)||({0,0}))[1] == -2)  // directory
+      foreach(get_dir(f)||({}), string file)
+	rm(f+"/"+file);  // delete file
+    rm(f);  // delete file/directory
+  }
+
+  void purge()
+  {
+    // remove whole db...
+    LOCK();
+    foreach(values(buckets)+values(tables)+({ logfile }), object o)
+      destruct(o);
+    buckets = 0;
+    tables = 0;
+    level2_rm(dir+"Buckets");
+    level2_rm(dir);
+    dir = 0;
+    destruct();
+    UNLOCK();
+  }
+
+  static void restore_logs()
+  {
+    string log = "\n"+(Stdio.read_file(dir+"log.2")||"")+
+      (Stdio.read_file(dir+"log.1")||"")+"\n";
+    int p=-1, d1_pos=-1, d2_pos=-1;
+    while((p=search(log, "\nD", p+1))>=0)
+      if(log[p+2]=='1')
+	d1_pos = p;
+      else if(log[p+2]=='2')
+	d2_pos = d1_pos;
+    if(d2_pos >= 0)
+      log = log[d2_pos..];
+
+    mapping(int:array(array)) bucket_log = ([]);
+    mapping(string:array(array)) table_log = ([]);
+
+    foreach(log/"\n", string entry) {
+      int main, sub;
+      array a;
+      if(sscanf(entry, "%c%c%s", main, sub, entry)==3 &&
+	 !catch(a = decode_value(replace(entry, ({ "\203\203", "\203n" }),
+					 ({ "\203", "\n" })))))
+	switch(main) {
+	case 'D':
+	  break;
+	case 'T':
+	  if(table_log[a[0]])
+	    table_log[a[0]] += ({ ({ sub }) + a[1..] });
+	  else
+	    table_log[a[0]] = ({ ({ sub }) + a[1..] });
+	  break;
+	case 'B':
+	  if(bucket_log[a[0]])
+	    bucket_log[a[0]] += ({ ({ sub }) + a[1..] });
+	  else
+	    bucket_log[a[0]] = ({ ({ sub }) + a[1..] });
+	  break;
+	  break;
+	}
+    }
+    log = 0;
+
+    foreach(indices(bucket_log), int bucket)
+      get_bucket(bucket)->restore_from_log(bucket_log[bucket]);
+    foreach(indices(table_log), string t)
+      table(t)->restore_from_log(table_log[t]);
+  }
+  
+  void create(string d, string mode)
+  {
+    if(search(mode,"e")+1) exceptions=1;
+    if(search(mode,"w")+1) write=1;
+    if(search(mode,"C")+1) compress=1;
+    if(search(mode,"s")+1) sov=1;
+    if(search(mode,"c")+1) if(!file_stat(d))
+    {
+      mkdirhier(d+"/foo");
+      mkdirhier(d+"/Buckets/foo");
+    }
+    dir = replace(d+"/","//","/");
+    logfile = 0;
+    restore_logs();
+    if (write) {
+      logfile = Stdio.File();
+      logfile->open(dir+"log.1", "cwa");
+      if(logfile->write("\n")!=1) {
+	destruct(this_object());
+	PDB_ERR("Failed to write a single byte to log.");
+      }
+      sync();
+    }
+  }
+};