/* NoteServ gdbm database examiner/debugger
** A generic gdbm file dumper
**
** W. Campbell (20 fév 2002)
** wcampbel@botbay.net
**
** $Id: ns-dumpdb.c,v 1.5 2003/01/14 00:51:14 wcampbel Exp $
**
** This code can be used in any way you please as long as
** my name remains with it.
*/

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include "gdbm.h"

#define TRUE 1
#define FALSE 0

#define TYPE_UNDEF	0
#define TYPE_LAST	1
#define TYPE_USER	2

/* These are NoteServ's structs and additional definitions */
#define HOSTLEN         63   
#define NICKLEN          9
#define USERLEN         10
#define KEYLEN          NICKLEN + USERLEN + HOSTLEN + 3
#define PASSWDLEN       20
#define EXPRLEN         KEYLEN
#define MSGLEN          512
#define SERVERNAMELEN   50

struct Last
{ 
    char key[KEYLEN + 1];
    time_t last;
};

struct Expression
{
    char pattern[EXPRLEN + 1];
    char comment[EXPRLEN + 1];
    time_t timeout;
    struct Expression *next;
};

struct Message
{
    char pattern[EXPRLEN + 1];
    char message[MSGLEN + 1];
    time_t timeout;
    time_t timestamp;
    struct Message *next;
};

struct Clients
{   
    char key[KEYLEN + 1];
    char username[USERLEN + 1];
    char password[PASSWDLEN + 1];
    char server[SERVERNAMELEN + 1];
    int status;
    time_t timestamp;
    struct Expression *expressions;
    int expr_count;
    struct Message *messages;
    int msg_count;
    struct Clients *next;
};


void usage(char *);
void print_content(FILE *, char *, void *, unsigned int);
struct Clients *read_client(void *);
void print_client(FILE *, char *, struct Clients *);
void free_client(struct Clients *);

int main(int argc, char *argv[])
{
  char c;
  char *file_name = NULL;
  char *output_file = NULL;
  GDBM_FILE dbf;
  datum key;
  datum content;
  datum nextkey;
  extern char *optarg;
  FILE *outf = stdout;
  int outisfile = FALSE;
  unsigned int type = TYPE_UNDEF;

  while( (c = getopt(argc, argv, "f:o:ulh?")) != -1)
  {
    switch(c)
    {
      case 'h':
      case '?':
        usage(argv[0]);
        exit(0);
        /* NOT REACHED */
        break;
      case 'f':
        file_name = optarg;
        break;
      case 'o':
        output_file = optarg;
        break;
      case 'u':
        type = TYPE_USER;
        break;
      case 'l':
        type = TYPE_LAST;
        break;
      default:
        usage(argv[0]);
        exit(0);
        /* NOT REACHED */
        break;
    }
  }

  if (file_name == NULL)
  {
    fprintf(stderr, "No filename specified.\n");
    usage(argv[0]);
    exit(1);
  }

  if (output_file != NULL)
  {
    outf = fopen(output_file, O_RDONLY);
    outisfile = TRUE;
    if (outf == NULL)
    {
      fprintf(stderr, "Error opening output file: %s (%s)\n", output_file,
              strerror(errno));
      fprintf(stderr, "Terminating\n");
      exit(2);
    }
  }

  /* Open the gdbm file with the default blocksize */
  dbf = gdbm_open(file_name, 0, GDBM_READER, 00600, NULL);

  if (dbf == NULL)
  {
    fprintf(stderr, "GDBM open failed: %s\n", gdbm_strerror(gdbm_errno));
    exit(3);
  }

  /* Process the gdbm hash, extracting each key and value pair */
  key = gdbm_firstkey(dbf);
  while (key.dptr != NULL)
  {
    nextkey = gdbm_nextkey(dbf, key);
    content = gdbm_fetch(dbf, key);
    print_content(outf, key.dptr, content.dptr, type);
    free(content.dptr);
    free(key.dptr);
    key = nextkey;
  }

  if (outisfile == TRUE)
  {
    fclose(outf);
  }

  gdbm_close(dbf);

  return 0;
}

void usage(char *arg0)
{
  fprintf(stderr, "%s [-l|-u] -f file_name [-o output_file]\n", arg0);
}

void print_content(FILE *outf, char *key, void *content, unsigned int type)
{
  struct Last *lptr;
  struct Clients *cptr;

  switch(type)
  {
    case TYPE_UNDEF:
      /* Process it as if it were a simple char * pointer */
      fprintf(outf, "%s => %s\n", key, content);
      break;
    case TYPE_LAST:
      /* lastlog entry for NoteServ */
      lptr = (struct Last *) content;
      fprintf(outf, "%s => (%ld) %s\n", key, lptr->last, lptr->key);
      break;
    case TYPE_USER:
      /* user/client entry for NoteServ */
      cptr = read_client(content);
      print_client(outf, key, cptr);
      free_client(cptr);
      break;
  }
}

/* This function is pretty much straight out of NoteServ */
struct Clients *read_client(void *content)
{
  struct Clients *cptr;
  struct Expression *eptr, *last_eptr;
  struct Message *mptr, *last_mptr;
  char *ptr;
  int i;

  ptr = content;

  cptr = malloc(sizeof(struct Clients));
  memcpy(cptr, ptr, sizeof(struct Clients));

  ptr += sizeof(struct Clients);
  cptr->expressions = NULL;
  cptr->messages = NULL;

  for (i = 1; i <= cptr->expr_count; i++)
  {
    eptr = malloc(sizeof(struct Expression));
    memcpy(eptr, ptr, sizeof(struct Expression));
    eptr->next = NULL;
    ptr += sizeof(struct Expression);

    if (i == 1)
      cptr->expressions = eptr;
    else
      last_eptr->next = eptr;

    last_eptr = eptr;
  }

  for (i = 1; i <= cptr->msg_count; i++)
  {
    mptr = malloc(sizeof(struct Message));
    memcpy(mptr, ptr, sizeof(struct Message));
    mptr->next = NULL;
    ptr += sizeof(struct Message);

    if (i == 1)
      cptr->messages = mptr;
    else
      last_mptr->next = mptr;

    last_mptr = mptr;
  }

  return cptr;
}

void free_client(struct Clients *cptr)
{
  struct Expression *eptr, *last_eptr;
  struct Message *mptr, *last_mptr;

  eptr = cptr->expressions;

  while (eptr != NULL)
  {
    last_eptr = eptr;
    eptr = eptr->next;
    free(last_eptr);
  }

  mptr = cptr->messages;

  while (mptr != NULL)
  {
    last_mptr = mptr;
    mptr = mptr->next;
    free(last_mptr);
  }

  free(cptr);
}

void print_client(FILE *outf, char *key, struct Clients *cptr)
{
  struct Expression *eptr;
  struct Message *mptr;

  fprintf(outf, "%s => username: %s  password: %s\n", key, cptr->username,
          cptr->password);
  fprintf(outf, "      key: %s  timestamp: %ld\n", cptr->key, cptr->timestamp);
  
  eptr = cptr->expressions;
  while (eptr != NULL)
  {
    fprintf(outf, "      EXP (timeout %ld) pattern: %s  comment: %s\n",
            eptr->timeout, eptr->pattern, eptr->comment);
    eptr = eptr->next;
  }
  mptr = cptr->messages;
  while (mptr != NULL)
  {
    fprintf(outf, "      MSG (timeout %ld)(timestamp %ld) pattern: %s\n",
            mptr->timeout, mptr->timestamp, mptr->pattern);
    fprintf(outf, "          message: %s\n", mptr->message);
    mptr = mptr->next;
  }
}

