/*++++++++++++++++++++
refdbda.c: refdb application server, administrative functions
markus@mhoenicka.de 6-5-00

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.
   
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
   
   You should have received a copy of the GNU General Public License
   along with this program; if not, see <http://www.gnu.org/licenses/>

   +++++++++++++++++++++++++*/


#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h> /* for umask */
#include <unistd.h>
#include <syslog.h>
#include <fcntl.h>
#include <time.h> /* for strftime */
#include <math.h> /* for log10 */
#include <sys/types.h>
#include <sys/socket.h>
#include <dbi/dbi.h>

#include "backend.h"
#include "refdb.h"
#include "linklist.h"
#include "refdbd.h" /* depends on backend.h */
#include "tokenize.h"
#include "strfncs.h"
#include "connect.h"
#include "cgi.h"
#include "dbfncs.h"

/* these are defined in refdbd.c */
extern char server_ip[];
extern char log_file[];
extern char log_dest[];
extern char log_level[];
extern char port_address[];
extern char dbs_port_address[];
extern char refdb_timeout[];
extern char refdblib[];
extern char the_fifo[];
extern char pid_file[];
extern char cs_term[];
extern char main_db[];
extern char force_dbcheck[];
extern int n_log_level;
extern int n_log_dest;
extern int n_refdb_timeout;
extern int n_reopen_log;
extern int n_remote_admin;
extern int run;
extern int parent_pid;
extern int fd_fifo;

/* forward declarations of local functions */
static int is_reference_database(struct CLIENT_REQUEST* ptr_clrequest, dbi_conn conn, const char* dbname);
static int daemon_started_by_init(void);
static int daemon_started_by_inetd(void);
static long limit_open(void);

void log_dberror(dbi_conn Conn, void *user_argument);

/* declaration of the svn version function */
const char* svn_version(void);

/* additional types */
#ifndef HAVE_SOCKLEN_T
#  define socklen_t int
#endif


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  child_confserv(): configures the application server (child process)
                    this fn sends the changes to the parent process
                    via a FIFO/named pipe (via a temp file in Cygwin).

  int child_confserv returns 0 if ok, >0 if error

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  struct ADDRESULT* ptr_addresult ptr to struct with counters

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int child_confserv(struct CLIENT_REQUEST* ptr_clrequest, struct ADDRESULT* ptr_addresult) {
  char outbuffer[OUTBUF_LEN];
  int fd_fifo = 0;
  int retval;

  if (!n_remote_admin) {
    send_status(ptr_clrequest->fd, 0, TERM_NO);
    tiwrite(ptr_clrequest->fd, "703\n", TERM_YES);
    ptr_addresult->failure++;
    return 2;
  }

  /* check our access rights */
  retval = is_refdb_admin(ptr_clrequest, ptr_addresult);
  if (!retval) { /* unauthorized attempt likely */
    send_status(ptr_clrequest->fd, 0, TERM_NO);
    tiwrite(ptr_clrequest->fd, ptr_addresult->msg, TERM_YES);
    ptr_addresult->failure++;
    return 2;
  }
  else if (retval == -1) { /* connection failed */
    send_status(ptr_clrequest->fd, 202, TERM_NO);
    LOG_PRINT(LOG_WARNING, get_status_msg(202));
    return 1;
  }

  /* we have to treat ping separately as it should not
     create a temp file or use the FIFO */
  if (ptr_clrequest->inargc == *(ptr_clrequest->ptr_optind)+1 && strcmp(ptr_clrequest->inargv[*(ptr_clrequest->ptr_optind)], "ping") == 0) {
    send_status(ptr_clrequest->fd, 0, TERM_NO);
    sprintf(&(ptr_addresult->msg[strlen(ptr_addresult->msg)]), "707:parent %d:child %d\n", parent_pid, getpid());
    tiwrite(ptr_clrequest->fd, ptr_addresult->msg, TERM_YES);
    ptr_addresult->success++;
    return 0;
  }

#ifdef HAVE_MKFIFO
  if (fd_fifo != -1) {
    close(fd_fifo); /* this is still open for reading */
    if ((fd_fifo = open(the_fifo, O_WRONLY)) == -1) {
      LOG_PRINT(LOG_WARNING, get_status_msg(839));
      send_status(ptr_clrequest->fd, 839, TERM_NO);
      ptr_addresult->failure++;
      return 1;
    }
  }
  else {
    LOG_PRINT(LOG_WARNING, get_status_msg(839));
    send_status(ptr_clrequest->fd, 839, TERM_NO);
    ptr_addresult->failure++;
    return 1;
  }
#else
  if (access(the_fifo, F_OK) != -1) { /* old file still exists */
    LOG_PRINT(LOG_WARNING, get_status_msg(839));
    send_status(ptr_clrequest->fd, 839, TERM_NO);
    ptr_addresult->failure++;
    return 1;
  }
  else { /* create a temp file with exclusive rights */
    if ((fd_fifo = open(the_fifo, O_WRONLY|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR)) == -1) {
      LOG_PRINT(LOG_WARNING, get_status_msg(839));
      send_status(ptr_clrequest->fd, 839, TERM_NO);
      ptr_addresult->failure++;
      return 1;
    }
  }
#endif /* HAVE_MKFIFO */
  
  /* the inargv array should have one or two entries: a command and
     an optional argument */
  if (ptr_clrequest->inargc == *(ptr_clrequest->ptr_optind)) { /* no argument given */
      LOG_PRINT(LOG_WARNING, get_status_msg(301));
      send_status(ptr_clrequest->fd, 301, TERM_NO);
      ptr_addresult->failure++;
      return 1;
  }

  if (ptr_clrequest->inargc == *(ptr_clrequest->ptr_optind)+1) { /* one argument */
    if (strcmp(ptr_clrequest->inargv[*(ptr_clrequest->ptr_optind)], "stop") == 0) {
      if (write(fd_fifo, "1stop", 6) == -1) {
	LOG_PRINT(LOG_WARNING, get_status_msg(840));
	send_status(ptr_clrequest->fd, 840, TERM_NO);
	ptr_addresult->failure++;
	return 1;
      }
      else {
	sprintf(&(ptr_addresult->msg[strlen(ptr_addresult->msg)]), "708:%d:%d\n", parent_pid, getpid());
	ptr_addresult->success++;
	LOG_PRINT(LOG_INFO, get_status_msg(708));
      }
    }
    else {
      LOG_PRINT(LOG_WARNING, get_status_msg(301));
      send_status(ptr_clrequest->fd, 301, TERM_NO);
      ptr_addresult->failure++;
      return 1;
    }
  }
  else { /* more than one argument, use first two */
    if (strcmp(ptr_clrequest->inargv[*(ptr_clrequest->ptr_optind)], "serverip") == 0) {
      strcpy(ptr_clrequest->server_ip, ptr_clrequest->inargv[*(ptr_clrequest->ptr_optind)+1]);

      sprintf(outbuffer, "2serverip %s", ptr_clrequest->server_ip);
      if (write(fd_fifo, outbuffer, strlen(outbuffer) + 1) == -1) {
	LOG_PRINT(LOG_WARNING, get_status_msg(840));
	send_status(ptr_clrequest->fd, 840, TERM_NO);
	ptr_addresult->failure++;
	return 1;
      }
      else {
	sprintf(outbuffer, "709:%s", ptr_clrequest->server_ip);
	LOG_PRINT(LOG_INFO, outbuffer);
	sprintf(&(ptr_addresult->msg[strlen(ptr_addresult->msg)]), "709:%s\n", ptr_clrequest->server_ip);
	ptr_addresult->success++;
      }
    }
    else if (strcmp(ptr_clrequest->inargv[*(ptr_clrequest->ptr_optind)], "timeout") == 0) {
      strcpy(refdb_timeout, ptr_clrequest->inargv[*(ptr_clrequest->ptr_optind)+1]);
      n_refdb_timeout = atoi(refdb_timeout);

      sprintf(outbuffer, "2timeout %s", refdb_timeout);
      if (write(fd_fifo, outbuffer, strlen(outbuffer) + 1) == -1) {
	LOG_PRINT(LOG_WARNING, get_status_msg(840));
	send_status(ptr_clrequest->fd, 840, TERM_NO);
	ptr_addresult->failure++;
	return 1;
      }
      else {
	sprintf(outbuffer, "710:%d", n_refdb_timeout);
	LOG_PRINT(LOG_INFO, outbuffer);
	sprintf(&(ptr_addresult->msg[strlen(ptr_addresult->msg)]), "710:%d\n", n_refdb_timeout);
	ptr_addresult->success++;
      }
    }
    else if (strcmp(ptr_clrequest->inargv[*(ptr_clrequest->ptr_optind)], "logfile") == 0) {
      strcpy(log_file, ptr_clrequest->inargv[*(ptr_clrequest->ptr_optind)+1]);

      sprintf(outbuffer, "2logfile %s", log_file);
      if (write(fd_fifo, outbuffer, strlen(outbuffer) + 1) == -1) {
	LOG_PRINT(LOG_WARNING, get_status_msg(840));
	send_status(ptr_clrequest->fd, 840, TERM_NO);
	ptr_addresult->failure++;
	return 1;
      }
      else {
	sprintf(outbuffer, "711:%s", log_file);
	LOG_PRINT(LOG_INFO, outbuffer);
	sprintf(&(ptr_addresult->msg[strlen(ptr_addresult->msg)]), "711:%s\n", log_file);
	ptr_addresult->success++;
      }
    }
    else if (strcmp(ptr_clrequest->inargv[*(ptr_clrequest->ptr_optind)], "logdest") == 0) {
      strcpy(log_dest, ptr_clrequest->inargv[*(ptr_clrequest->ptr_optind)+1]);
      n_log_dest = atoi(log_dest);

      sprintf(outbuffer, "2logdest %s", log_dest);
      if (write(fd_fifo, outbuffer, strlen(outbuffer) + 1) == -1) {
	LOG_PRINT(LOG_WARNING, get_status_msg(840));
	send_status(ptr_clrequest->fd, 840, TERM_NO);
	ptr_addresult->failure++;
	return 1;
      }
      else {
	sprintf(outbuffer, "712:%d", n_log_dest);
	LOG_PRINT(LOG_INFO, outbuffer);
	sprintf(&(ptr_addresult->msg[strlen(ptr_addresult->msg)]), "712:%d\n", n_log_dest);
	ptr_addresult->success++;
      }
    }
    else if (strcmp(ptr_clrequest->inargv[*(ptr_clrequest->ptr_optind)], "loglevel") == 0) {
      strcpy(log_level, ptr_clrequest->inargv[*(ptr_clrequest->ptr_optind)+1]);
      n_log_level = atoi(log_level);

      sprintf(outbuffer, "2loglevel %s", log_level);
      if (write(fd_fifo, outbuffer, strlen(outbuffer) + 1) == -1) {
	LOG_PRINT(LOG_WARNING, get_status_msg(840));
	send_status(ptr_clrequest->fd, 840, TERM_NO);
	ptr_addresult->failure++;
	return 1;
      }
      else {
	sprintf(outbuffer, "713:%d", n_log_level);
	LOG_PRINT(LOG_INFO, outbuffer);
	sprintf(&(ptr_addresult->msg[strlen(ptr_addresult->msg)]), "713:%d\n", n_log_level);
	ptr_addresult->success++;
      }
    }
    else {
      LOG_PRINT(LOG_WARNING, get_status_msg(301));
      send_status(ptr_clrequest->fd, 301, TERM_NO);
      ptr_addresult->failure++;
      return 1;
    }
  }
  
  close(fd_fifo);

  /* send report to client */
  send_status(ptr_clrequest->fd, 0, TERM_NO);
  tiwrite(ptr_clrequest->fd, ptr_addresult->msg, TERM_YES);

  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  confserv(): configures the application server (parent process)

  void confserv

  char* command ptr to string with the command

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void confserv(char *command, char* server_ip) {
  char outbuffer[OUTBUF_LEN];

  /* the command buffer should have one or two entries: a command and
     an optional argument */
  if (*command == '1') { /* one argument */
    if (strncmp(command + 1, "stop", 4) == 0) {
      run = 0;
      LOG_PRINT(LOG_INFO, "application server stopped");
    }
  }
  else { /* more than one argument, use first two */
    if (strncmp(command + 1, "serverip", 8) == 0) {
      strcpy(server_ip, command + 10);
      sprintf(outbuffer, "set serverip to %s", server_ip);
      LOG_PRINT(LOG_INFO, outbuffer);
    }
    else if (strncmp(command + 1, "timeout", 7) == 0) {
      strcpy(refdb_timeout, command + 9);
      n_refdb_timeout = atoi(refdb_timeout);
      sprintf(outbuffer, "set timeout to %d seconds", n_refdb_timeout);
      LOG_PRINT(LOG_INFO, outbuffer);
    }
    else if (strncmp(command + 1, "logfile", 7) == 0) {
      strcpy(log_file, command + 9);
      sprintf(outbuffer, "set logfile to %s", log_file);
      LOG_PRINT(LOG_INFO, outbuffer);
      n_reopen_log = 1; /* signal the log fn to open new log file */
    }
    else if (strncmp(command + 1, "logdest", 7) == 0) {
      strcpy(log_dest, command + 9);
      n_log_dest = atoi(log_dest);
      sprintf(outbuffer, "set logdest to %d", n_log_dest);
      LOG_PRINT(LOG_INFO, outbuffer);
    }
    else if (strncmp(command + 1, "loglevel", 8) == 0) {
      strcpy(log_level, command + 10);
      n_log_level = atoi(log_level);
      sprintf(outbuffer, "set loglevel to %d", n_log_level);
      LOG_PRINT(LOG_INFO, outbuffer);
    }
  }
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  connect_to_db(): establishes a connection with the SQL database server
  
  dbi_conn connect_to_db Returns connection ptr if successful,
                       NULL if failed      

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with connection info

  const char* special_db ptr to string containing the name of a special
              database to connect to if the latter is different from
	      the one in ptr_clrequest

  int nocheck if 1, don't verify the database is a RefDB database

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
dbi_conn connect_to_db(struct CLIENT_REQUEST* ptr_clrequest, const char* special_db, int nocheck) {
  dbi_conn conn;
  char *my_db;

  LOG_PRINT(LOG_DEBUG, ptr_clrequest->server_ip);
  LOG_PRINT(LOG_DEBUG, ptr_clrequest->username);
  LOG_PRINT(LOG_DEBUG, ptr_clrequest->passwd);
  LOG_PRINT(LOG_DEBUG, ptr_clrequest->current_db);
  LOG_PRINT(LOG_DEBUG, ptr_clrequest->dbs_port_address);
  LOG_PRINT(LOG_DEBUG, ptr_clrequest->dbserver);
  LOG_PRINT(LOG_DEBUG, ptr_clrequest->db_path);
  LOG_PRINT(LOG_DEBUG, ptr_clrequest->db_encoding);

  conn = my_dbi_conn_new(ptr_clrequest->dbserver, ptr_clrequest->inst);

  if (!conn) {
    LOG_PRINT(LOG_WARNING, "creating database connection structure failed");
    LOG_PRINT(LOG_WARNING, "check your libdbi installation");
    return NULL;
  }

  my_db = (special_db && *special_db) ? (char*)special_db : ((*(ptr_clrequest->current_db)) ? ptr_clrequest->current_db : main_db);
  LOG_PRINT(LOG_DEBUG, my_db);
  /* common driver options */
  dbi_conn_set_option(conn, "username", ptr_clrequest->username);
  dbi_conn_set_option(conn, "password", (ptr_clrequest->passwd && *(ptr_clrequest->passwd)) ? ptr_clrequest->passwd : "");
  /* connect to refdb unless a specific reference database is requested
     special_db overrides ptr_clrequest->current_db */
  dbi_conn_set_option(conn, "dbname", my_db);
  dbi_conn_set_option(conn, "encoding", "auto");
  dbi_conn_set_option_numeric(conn, "port", atoi(ptr_clrequest->dbs_port_address));

  /* driver-specific options */
  if (!strcmp(ptr_clrequest->dbserver, "mysql")) {
    dbi_conn_set_option_numeric(conn, "compression", 1);
    dbi_conn_set_option(conn, "host", ptr_clrequest->server_ip);
    dbi_conn_set_option_numeric(conn, "mysql_include_trailing_null", 1);
  }
  else if (!strcmp(ptr_clrequest->dbserver, "pgsql")) {
    /* don't set host if we want to connect locally */
    if (strcmp(ptr_clrequest->server_ip, "localhost")) {
      dbi_conn_set_option(conn, "host", ptr_clrequest->server_ip);
    }
  }
  else if (!strcmp(ptr_clrequest->dbserver, "sqlite")) {
    dbi_conn_set_option(conn, "sqlite_dbdir", ptr_clrequest->db_path);
    dbi_conn_set_option_numeric(conn, "sqlite_timeout", ptr_clrequest->db_timeout);
  }
  else if (!strcmp(ptr_clrequest->dbserver, "sqlite3")) {
    dbi_conn_set_option(conn, "sqlite3_dbdir", ptr_clrequest->db_path);
    dbi_conn_set_option(conn, "encoding", "UTF-8");
    dbi_conn_set_option_numeric(conn, "sqlite3_timeout", ptr_clrequest->db_timeout);
  }

  if (dbi_conn_connect(conn) < 0) { /* -1 and -2 indicate errors */
    const char* errmsg = NULL;

    LOG_PRINT(LOG_WARNING, "failed to connect to database server using database:");
    LOG_PRINT(LOG_WARNING, my_db);
    dbi_conn_error(conn, &errmsg);
    if (errmsg) {
      LOG_PRINT(LOG_WARNING, errmsg);
    }
    dbi_conn_close(conn);
    return NULL;
  }

  if (!nocheck && strcmp(my_db, main_db) && !is_reference_database(NULL, conn, my_db)) {
/*   if ((!strcmp(force_dbcheck, "t") || !nocheck) && strcmp(my_db, MAIN_DB) && !is_reference_database(NULL, conn, my_db)) { */
    LOG_PRINT(LOG_WARNING, "does not appear to be a RefDB reference database:");
    LOG_PRINT(LOG_WARNING, my_db);
    dbi_conn_close(conn);
    return NULL;
  }

  /* register dbi error handler */
  dbi_conn_error_handler(conn, log_dberror, NULL);

  LOG_PRINT(LOG_DEBUG, "connected to database server using database:");
  LOG_PRINT(LOG_DEBUG, my_db);
  return conn;
}

void log_dberror(dbi_conn conn, void *user_argument) {
  const char *msg;

  dbi_conn_error(conn, &msg);
  LOG_PRINT(LOG_DEBUG, msg);
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  viewstat(): executes client command viewstat

  int viewstat returns 0 if ok, 1 if memory error, 2 if other error

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int viewstat(struct CLIENT_REQUEST* ptr_clrequest) {
  dbi_conn conn; /* libdbi connection structure */
  dbi_driver driver; /* libdbi driver structure */
  dbi_result dbires; /* libdbi result structure */
  char* outbuffer; /* ptr to a buffer for output */
  short int dbversioninfo = 0;
  char sql_command[64];
  char db_version[VERSIONSTRING_LENGTH];

  /* get a buffer to hold output data */
  if ((outbuffer = malloc((size_t)OUTBUF_LEN)) == NULL) {
    /* out of memory */
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    return 1;
  }
    
  /* connect to the database */
  if ((conn = connect_to_db(ptr_clrequest, NULL, 0)) != NULL) {
    driver = dbi_conn_get_driver(conn);

    sprintf(sql_command, "SELECT meta_dbversion FROM t_meta WHERE meta_type='refdb'");
    dbires = dbi_conn_query(conn, sql_command);
    LOG_PRINT(LOG_DEBUG, sql_command);
    if (dbires) {
      if (dbi_result_next_row(dbires)) {
	dbversioninfo = dbi_result_get_short_idx(dbires, 1); /* 1-base index */
      }

      dbi_result_free(dbires);
    }
    else {
      /* could not retrieve reference database metadata */
      LOG_PRINT(LOG_ERR, get_status_msg(208));
      send_status(ptr_clrequest->fd, 208, TERM_NO);
      free(outbuffer);
      return 2;
    }

    /* obtain database server version info */
    dbi_conn_get_engine_version_string(conn, db_version);
  
    sprintf(outbuffer, "You are served by: %s %s\nSVN revision: %s\nClient IP: %s\nConnected via %s driver (%s)\nto: %s\ndb version: %d\nserverip: %s\ntimeout: %s\ndbs_port: %s\nlogfile: %s\nlogdest: %s\nloglevel: %s\nremoteadmin: %s\npidfile: %s\n",
	    PACKAGE,
	    VERSION,
	    svn_version(),
	    ptr_clrequest->client_ip,
	    dbi_driver_get_name(driver),
	    dbi_driver_get_version(driver),
	    db_version,
	    dbversioninfo,
	    ptr_clrequest->server_ip,
	    refdb_timeout,
	    ptr_clrequest->dbs_port_address,
	    log_file,
	    log_dest,
	    log_level,
	    (n_remote_admin) ? "on" : "off",
	    pid_file);

    /* all fine */
    send_status(ptr_clrequest->fd, 0, TERM_NO);
    tiwrite(ptr_clrequest->fd, outbuffer, TERM_YES);
    dbi_conn_close(conn);
  }
  else {
    /* access denied to reference database */
    LOG_PRINT(LOG_WARNING, get_status_msg(204));
    send_status(ptr_clrequest->fd, 204, TERM_NO);
    free(outbuffer);
    return 2;
  }
  free(outbuffer);

  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  listdb(): implements the client commands listdb and selectdb

  int listdb returns 0 if ok, 1 if error

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  int select if 1, answers selectdb command, if 0, answers listdb command

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int listdb(struct CLIENT_REQUEST* ptr_clrequest, int select, struct ADDRESULT* ptr_addresult) {
  char* new_msg;
  const char* dbname;
  char message[128];

  dbi_conn conn;
  dbi_result dbires;

  /* connect to the database */
  if ((conn = connect_to_db(ptr_clrequest, NULL, 0)) != NULL) {
    if (*(ptr_clrequest->argument)) {
      /* this will list the databases that match the provided pattern */
      dbires = dbi_conn_get_db_list(conn, ptr_clrequest->argument);
    }
    else {
      /* this will list all databases */
      dbires = dbi_conn_get_db_list(conn, NULL);
    }

    if (select) {
      /* selectdb */

      if (dbi_result_next_row(dbires)) {
	/* this is just to prevent problems if the SQL server
	   allows longer database names than we expect */
	dbname = dbi_result_get_string_idx(dbires, 1); /* 1-based index */
	if (dbname) {
	  if (is_reference_database(ptr_clrequest, NULL, dbname)) {
	    snprintf(message, 128, "240:%s\n", dbname);

	    if ((new_msg = mstrcat(ptr_addresult->msg, message, &(ptr_addresult->msg_len), 0)) == NULL) {
	      send_status(ptr_clrequest->fd, 801, TERM_NO);
	      LOG_PRINT(LOG_INFO, get_status_msg(801));
	      dbi_result_free(dbires);
	      dbi_conn_close(conn);
	      return 1;
	    }
	    else {
	      ptr_addresult->msg = new_msg;
	    }
	  }
	  else { /* is not a reference database */
	    send_status(ptr_clrequest->fd, 225, TERM_NO);
	    LOG_PRINT(LOG_INFO, get_status_msg(225));
	    dbi_result_free(dbires);
	    dbi_conn_close(conn);
	    return 1;
	  }
	}
	else { /* database does not exist */
	  send_status(ptr_clrequest->fd, 226, TERM_NO);
	  LOG_PRINT(LOG_INFO, get_status_msg(226));
	  dbi_result_free(dbires);
	  dbi_conn_close(conn);
	  return 1;
	}
      }
      else { /* database does not exist */
	send_status(ptr_clrequest->fd, 226, TERM_NO);
	LOG_PRINT(LOG_INFO, get_status_msg(226));
	dbi_result_free(dbires);
	dbi_conn_close(conn);
	return 1;
      }
    }
    else {
      /* listdb */

      while (dbi_result_next_row(dbires)) {
	dbname = dbi_result_get_string_idx(dbires, 1);
	if (dbname) {
	  if (is_reference_database(ptr_clrequest, NULL, dbname)) {
	    snprintf(message, 128, "%s\n", dbname);

	    if ((new_msg = mstrcat(ptr_addresult->msg, message, &(ptr_addresult->msg_len), 0)) == NULL) {
	      send_status(ptr_clrequest->fd, 801, TERM_NO);
	      LOG_PRINT(LOG_INFO, get_status_msg(801));
	      dbi_result_free(dbires);
	      dbi_conn_close(conn);
	      return 1;
	    }
	    else {
	      ptr_addresult->msg = new_msg;
	    }
	    ptr_addresult->success++;
	  }
	}
      }
    }
    dbi_result_free(dbires);
    dbi_conn_close(conn);

    send_status(ptr_clrequest->fd, 0, TERM_NO);
    tiwrite(ptr_clrequest->fd, ptr_addresult->msg, TERM_YES);
  }
  else {
    LOG_PRINT(LOG_WARNING, get_status_msg(202));
    send_status(ptr_clrequest->fd, 202, TERM_NO);
    return 1;
  }

  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  is_reference_database(): checks whether a database is a RefDB
                           reference database

  int is_reference_database returns 1 if the database is a RefDB database,
                            0 if not

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info
                         set to NULL if using conn

  dbi_conn conn optional existing connection to database. Overrides
                         ptr_clrequest. Set to NULL if not used

  const char* dbname ptr to a string containing the database name

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int is_reference_database(struct CLIENT_REQUEST* ptr_clrequest, dbi_conn conn, const char* dbname) {
  dbi_conn conn_ref;
  dbi_result dbires;
  char sql_command[128];
  const char* app;
  const char* type;
  int retval = 0;
  int close_conn_ref = 0;
  short int dbversion;

  if (conn) {
    conn_ref = conn; /* legal as conn and conn_ref are pointers */
  }
  else {
    if ((conn_ref = connect_to_db(ptr_clrequest, dbname, 1)) == NULL) {
      /* can't access */
      return 0;
    }
    close_conn_ref++;
  }

  sprintf(sql_command, "SELECT meta_app,meta_type,meta_dbversion FROM t_meta WHERE meta_type='risx'");

  LOG_PRINT(LOG_DEBUG, sql_command);
  dbires = dbi_conn_query(conn_ref, sql_command);

  if (!dbires) {
    goto Finish;
  }

  if (!dbi_result_next_row(dbires)) {
    goto Finish;
  }

  app = dbi_result_get_string(dbires, "meta_app");
  type = dbi_result_get_string(dbires, "meta_type");
  dbversion = dbi_result_get_short(dbires, "meta_dbversion");

  if (!strcmp(app, "refdb")
      && (!strcmp(type, "ris") || !strcmp(type, "risx"))
      && dbversion >= MIN_DB_VERSION) {
    retval = 1;
  }

 Finish:
  if (close_conn_ref) {
    dbi_conn_close(conn_ref);
  }

  return retval;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  listuser(): implements the client command listuser

  int listuser returns 0 if ok, >0 if error

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  struct ADDRESULT* ptr_addresult ptr to struct with counters

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int listuser(struct CLIENT_REQUEST* ptr_clrequest, struct ADDRESULT* ptr_addresult) {
  dbi_conn conn;
  dbi_result dbires;
  dbi_driver driver;
  char sql_command[256];
  const char* username;
  char* myarg;
  char* new_msg;

  /* connect to the database */
  if ((conn = connect_to_db(ptr_clrequest, ptr_clrequest->current_db, 0)) != NULL) {
    driver = dbi_conn_get_driver(conn);

    if (ptr_clrequest->argument && *(ptr_clrequest->argument)) {
      myarg = strdup(ptr_clrequest->argument);
    }
    else {
      myarg = strdup(my_dbi_driver_get_cap(driver, "listall")); /* list all if no argument is specified */
    }
    if (!myarg) {
      send_status(ptr_clrequest->fd, 801, TERM_NO);
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      dbi_conn_close(conn);
      return 1;
    }

    /* escape any characters that the database server cannot digest */
    if (dbi_conn_quote_string(conn, &myarg) == 0) {
      dbi_conn_close(conn);
      free(myarg);
      send_status(ptr_clrequest->fd, 801, TERM_NO);
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 1;
    }

    snprintf(sql_command, 256, "SELECT DISTINCT user_name FROM t_user WHERE user_name %s %s", my_dbi_conn_get_cap(conn, "rlike"), myarg);
    dbires = dbi_conn_query(conn, sql_command);
    LOG_PRINT(LOG_DEBUG, sql_command);

    if (dbires) {
      while (dbi_result_next_row(dbires)) {
	username = dbi_result_get_string(dbires, "user_name");
	if (username) {
	  /* reuse sql_command */
	  sprintf(sql_command, "%s\n", username);
	  if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
	    dbi_conn_close(conn);
	    free(myarg);
	    send_status(ptr_clrequest->fd, 801, TERM_NO);
	    LOG_PRINT(LOG_CRIT, get_status_msg(801));
	    return 1;
	  }
	  else {
	    ptr_addresult->msg = new_msg;
	  }
	  ptr_addresult->success++;
	}
      }
      dbi_result_free(dbires);
    }
    else {
      /* select failed */
      dbi_conn_close(conn);
      free(myarg);
      send_status(ptr_clrequest->fd, 234, TERM_NO);
      LOG_PRINT(LOG_DEBUG, get_status_msg(234));
      return 1;
    }
    dbi_conn_close(conn);
    free(myarg);

    /* send back result to client */
    send_status(ptr_clrequest->fd, 0, TERM_NO);
    tiwrite(ptr_clrequest->fd, ptr_addresult->msg, TERM_YES);

    return 0;
  }
  else {
    send_status(ptr_clrequest->fd, 204, TERM_NO);
    LOG_PRINT(LOG_WARNING, get_status_msg(204));
    return 1;
  }
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  listword(): implements the client command listword

  int listword returns 0 if ok, >0 if error

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  struct ADDRESULT* ptr_addresult ptr to struct with counters

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int listword(struct CLIENT_REQUEST* ptr_clrequest, struct ADDRESULT* ptr_addresult) {
  dbi_conn conn;
  dbi_result dbires;
  dbi_driver driver;
  char sql_command[512];
  const char* wordname;
  char* myarg;

  /* connect to the database */
  if ((conn = connect_to_db(ptr_clrequest, NULL, 0)) != NULL) {
    driver = dbi_conn_get_driver(conn);

    if (ptr_clrequest->argument && *(ptr_clrequest->argument)) {
      myarg = strdup(ptr_clrequest->argument);
    }
    else {
      myarg = strdup(my_dbi_driver_get_cap(driver, "listall")); /* list all if no argument is specified */
    }

    if (!myarg) {
      dbi_conn_close(conn);
      send_status(ptr_clrequest->fd, 801, TERM_NO);
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 1;
    }

    /* escape any characters that the database server cannot digest */
    if (dbi_conn_quote_string(conn, &myarg) == 0) {
      dbi_conn_close(conn);
      free(myarg);
      send_status(ptr_clrequest->fd, 801, TERM_NO);
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 1;
    }

    snprintf(sql_command, 512, "SELECT DISTINCT name FROM t_journal_words WHERE name %s %s", my_dbi_conn_get_cap(conn, "rlike"), myarg);
    LOG_PRINT(LOG_DEBUG, sql_command);

    dbires = dbi_conn_query(conn, sql_command);
    if (dbires) {
      send_status(ptr_clrequest->fd, 0, TERM_NO);

      while (dbi_result_next_row(dbires)) {
	wordname = dbi_result_get_string(dbires, "name");
	if (wordname) {
	  tiwrite(ptr_clrequest->fd, wordname, TERM_NO);
	  tiwrite(ptr_clrequest->fd, "\n", TERM_NO);
	  ptr_addresult->success++;
	}
	else {
	  ptr_addresult->failure++;
	}
      }
      dbi_result_free(dbires);

      /* terminate message */
      tiwrite(ptr_clrequest->fd, "", TERM_YES);
    }
    else {
      /* select failed */
      dbi_conn_close(conn);
      free(myarg);
      send_status(ptr_clrequest->fd, 234, TERM_NO);
      LOG_PRINT(LOG_DEBUG, get_status_msg(234));
      return 1;
    }

    dbi_conn_close(conn);
    free(myarg);

    return 0;
  }
  else {
    send_status(ptr_clrequest->fd, 204, TERM_NO);
    LOG_PRINT(LOG_WARNING, get_status_msg(204));
    return 1;
  }
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  createdb(): implements the client command createdb

  int createdb returns 2 if error, 0 if successful 

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  const char* db_encoding name of the character encoding to use

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int createdb(struct CLIENT_REQUEST* ptr_clrequest, struct ADDRESULT* ptr_addresult, int optind) {
  int result;
  char* new_msg;
  char message[128];

  /* loop over all filename arguments */
  for (; optind < ptr_clrequest->inargc; optind++) {
    result = real_createdb(ptr_clrequest, ptr_clrequest->inargv[optind], ptr_addresult);
    if (result == 1) {
      ptr_addresult->failure++;
      /* message is already in place */
      send_status(ptr_clrequest->fd, 801, TERM_NO);
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      return 1;
    }
    else if (result) {
      ptr_addresult->failure++;
      /* message is already in place */
    }
    else {
      ptr_addresult->success++;
      snprintf(message, 128, "235:%s\n", ptr_clrequest->inargv[optind]);

      if ((new_msg = mstrcat(ptr_addresult->msg, message, &(ptr_addresult->msg_len), 0)) == NULL) {
	send_status(ptr_clrequest->fd, 801, TERM_NO);
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	return 1;
      }
      else {
	ptr_addresult->msg = new_msg;
      }
    }
  }

  send_status(ptr_clrequest->fd, 0, TERM_NO);
  tiwrite(ptr_clrequest->fd, ptr_addresult->msg, TERM_YES);

  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  real_createdb(): creates a database

  int real_createdb returns 1 if mem error, 2 if other error, 0 if successful 

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  const char* dbname ptr to string with the database name

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int real_createdb(struct CLIENT_REQUEST* ptr_clrequest, const char* dbname, struct ADDRESULT* ptr_addresult) {
  dbi_conn conn = NULL;
  dbi_result dbires;
  int error;
  int retval = 0;
  char date_buffer[24];
  char message[128] = "";
  char *sql_command = NULL;
  char *new_msg;
  const char *drivername;
  time_t the_time;

  /* it is not necessary to quote database names */

  /* connect to the main database */
  if ((conn = connect_to_db(ptr_clrequest, main_db, 0)) == NULL) {
    LOG_PRINT(LOG_WARNING, get_status_msg(202));
    snprintf(message, 128, "202:%s\n", dbname);
    retval = 2;
    goto finish;
  }
  
  drivername = dbi_driver_get_name(dbi_conn_get_driver(conn));

  /* allocate a buffer to assemble the sql command (give some extra byte
     for the command and encoding strings */
  sql_command = malloc((size_t)(strlen(dbname) + 256));
  if (sql_command == NULL) { /* malloc failed */
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    snprintf(message, 128, "801:%s\n", dbname);
    retval = 1;
    goto finish;
  }

  /* create a new database. There is no such SQL command for sqlite.
   sqlite will create the database for us as soon as we try to access
   it */

  if (strcmp(drivername, "sqlite")
      && strcmp(drivername, "sqlite3")) {
    const char* encoding_string;

    encoding_string = my_dbi_conn_get_cap(conn, "encoding");

    /* assemble sql command */
    if (*encoding_string) {
      if (ptr_clrequest->db_encoding && *(ptr_clrequest->db_encoding)) {
	sprintf(sql_command, "CREATE DATABASE %s %s '%s'", dbname, encoding_string, dbi_driver_encoding_from_iana(dbi_conn_get_driver(conn), ptr_clrequest->db_encoding));
      }
      else { /* use default encoding of db server */
	sprintf(sql_command, "CREATE DATABASE %s", dbname);
      }
    }
    else {
      if (ptr_clrequest->db_encoding && *(ptr_clrequest->db_encoding)) {
	LOG_PRINT(LOG_WARNING, "database engine does not support per-database character encodings, used default encoding instead");
      }

      /* db engine does not support database-based encodings */
      sprintf(sql_command, "CREATE DATABASE %s", dbname);
    }

    LOG_PRINT(LOG_DEBUG, sql_command);

    dbires = dbi_conn_query(conn, sql_command);

    if (dbires == NULL) {
      LOG_PRINT(LOG_ERR, get_status_msg(209));
      snprintf(message, 128, "209:%s\n", dbname);
      retval = 2;
      goto finish;
    }
    
    dbi_result_free(dbires);
  }
  else if (ptr_clrequest->db_encoding && *(ptr_clrequest->db_encoding)) { /* if sqlite */
    LOG_PRINT(LOG_WARNING, "database engine does not support per-database character encodings, used default encoding instead");
  } /* end if not sqlite */

  /* now we have to create the tables */
  if (!strcmp(my_dbi_conn_get_cap(conn, "multiple_db"), "t")) {
    /* we switch over to the new database */
    error = dbi_conn_select_db(conn, dbname);
    if (error) {
      LOG_PRINT(LOG_WARNING, get_status_msg(204));
      snprintf(message, 128, "204:%s\n", dbname);
      retval = 2;
      goto finish;
    }
    /* if (!strcmp(drivername, "mysql")) { */
    if ((error = create_tables_mysql(conn, ptr_clrequest, 0)) != 0) {
      /* function provides log message */
      snprintf(message, 128, "%d:%s\n", error, dbname);
      retval = 2;
      goto finish;
    }
  }
  else {
    /* we need a new connection to the fresh database */
    dbi_conn_close(conn);

    /* do not check the database as it is brand new */
    if ((conn = connect_to_db(ptr_clrequest, dbname, 1)) == NULL) {
      LOG_PRINT(LOG_WARNING, get_status_msg(204));
      snprintf(message, 128, "204:%s\n", dbname);
      retval = 2;
      goto finish;
    }
  
    if (!strcmp(drivername, "pgsql")) {
      if ((error = create_tables_pgsql(conn, ptr_clrequest, 0)) != 0) {
	/* function provides log message */
	snprintf(message, 128, "%d:%s\n", error, dbname);
	retval = 2;
	goto finish;
      }
    }
    else if (!strcmp(drivername, "sqlite")) {
      if ((error = create_tables_sqlite(conn, ptr_clrequest, 0)) != 0) {
	/* function provides log message */
	snprintf(message, 128, "%d:%s\n", error, dbname);
	retval = 2;
	goto finish;
      }
    }
    else if (!strcmp(drivername, "sqlite3")) {
      if ((error = create_tables_sqlite3(conn, ptr_clrequest, 0)) != 0) {
	/* function provides log message */
	snprintf(message, 128, "%d:%s\n", error, dbname);
	retval = 2;
	goto finish;
      }
    }
    /* else: should never happen */
  }

  /* insert the meta data */
  time(&the_time);
  strftime(date_buffer, 24, "%Y-%m-%d %H:%M:%S", gmtime(&the_time));
  sprintf(sql_command, "INSERT INTO t_meta (meta_app,meta_type,meta_version,meta_dbversion,meta_create_date,meta_modify_date) VALUES (\'%s\', \'risx\', \'%s\', \'%d\', \'%s\', \'%s\')", PACKAGE, VERSION, DB_VERSION, date_buffer, date_buffer);
  LOG_PRINT(LOG_DEBUG, sql_command);

  dbires = dbi_conn_query(conn, sql_command);

  if (!dbires) {
    LOG_PRINT(LOG_ERR, get_status_msg(210));
    snprintf(message, 128, "210:%s\n", dbname);
    retval = 2;
    goto finish;
  }
   
  dbi_result_free(dbires);

 finish:

  if (*message) {
    if ((new_msg = mstrcat(ptr_addresult->msg, message, &(ptr_addresult->msg_len), 0)) == NULL) {
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      retval = 1;
    }
    else {
      ptr_addresult->msg = new_msg;
    }
  }

  /* cleanup */
  dbi_conn_close(conn);
  free(sql_command);
      
  return retval;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  deletedb(): implements the client command deletedb

  int deletedb returns 0 if failed, 1 if successful

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  int optind index for inargv

  struct ADDRESULT* ptr_addresult ptr to struct with counters

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int deletedb(struct CLIENT_REQUEST* ptr_clrequest, int optind, struct ADDRESULT* ptr_addresult) {
  dbi_conn conn;
  dbi_result dbires;
  int error;
  char* sql_command;
  char* dbname;
  char* new_msg;
  const char* drivername;

  /* it is not necessary to quote the database name */

  /* allocate a buffer to assemble the sql command. We require
     _POSIX_PATH_MAX for the sqlite driver, but this will be more than
     enough for database names of other drivers */
  sql_command = malloc((size_t)_POSIX_PATH_MAX);
  if (sql_command == NULL) { /* malloc failed */
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }

  /* connect to main database */
  if ((conn = connect_to_db(ptr_clrequest, NULL, 0)) == NULL) {
    send_status(ptr_clrequest->fd, 202, TERM_NO);
    LOG_PRINT(LOG_WARNING, get_status_msg(202));
    return 1;
  }

  drivername = dbi_driver_get_name(dbi_conn_get_driver(conn));

  for (; optind < ptr_clrequest->inargc; optind++) {
    dbname = ptr_clrequest->inargv[optind];

    error = 0;

    if (!strcmp(drivername, "sqlite")
	|| !strcmp(drivername, "sqlite3")) {
      /* sqlite has no SQL way to drop databases. Try to remove the
	 database file from the file system manually */
      strcpy(sql_command, ptr_clrequest->db_path);
      if (sql_command[strlen(sql_command)-1] != '/') {
	strcat(sql_command, "/");
      }
      strcat(sql_command, dbname);
      
      if (unlink(sql_command)) {
	/* todo: ERRNO contains reason of failure */
	sprintf(sql_command, "249:%s\n", dbname);
	if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
	  send_status(ptr_clrequest->fd, 801, TERM_NO);
	  LOG_PRINT(LOG_CRIT, get_status_msg(801));
	  free(sql_command);
	  return 1;
	}
	else {
	  ptr_addresult->msg = new_msg;
	}
	error++;
      }
    }
    else {
      /* other databases */

      /* assemble sql command */
      strcpy(sql_command, "DROP DATABASE ");
      strcat(sql_command, dbname);

      LOG_PRINT(LOG_DEBUG, sql_command);

      dbires = dbi_conn_query(conn, sql_command);

      /* Postgres does not set numrows_affected for DROP statements so
	 we can't check easily whether the database was dropped */
      if (!dbires
	  || (strcmp(drivername, "pgsql")
	      && !dbi_result_get_numrows_affected(dbires))) {
	sprintf(sql_command, "249:%s\n", dbname);
	if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
	  send_status(ptr_clrequest->fd, 801, TERM_NO);
	  LOG_PRINT(LOG_CRIT, get_status_msg(801));
	  free(sql_command);
	  return 1;
	}
	else {
	  ptr_addresult->msg = new_msg;
	}
	error++;
      }
      else {
	dbi_result_free(dbires);
      }

      if (!strcmp(drivername, "pgsql")) {
	/* With PostgreSQL we keep a group to manage access rights for each
	   database. When dropping the database we drop the group too. This
	   does not affect the existence of the users contained in this group */
	if (is_group_pgsql(conn, dbname, 0 /* read/write */) == 1) {
	  sprintf(sql_command, "DROP GROUP %suser", ptr_clrequest->argument);
	  
	  LOG_PRINT(LOG_DEBUG, sql_command);

	  dbires = dbi_conn_query(conn, sql_command);
	  if (!dbires /*dbi_conn_error_flag(conn)*/) {
	    sprintf(sql_command, "250:%s\n", dbname);
	    if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
	      send_status(ptr_clrequest->fd, 801, TERM_NO);
	      LOG_PRINT(LOG_CRIT, get_status_msg(801));
	      free(sql_command);
	      return 1;
	    }
	    else {
	      ptr_addresult->msg = new_msg;
	    }
	  }
	  else {
	    dbi_result_free(dbires);
	  }
	}

	if (is_group_pgsql(conn, dbname, 1 /* read-only */) == 1) {
	  sprintf(sql_command, "DROP GROUP %sruser", ptr_clrequest->argument);
	  
	  LOG_PRINT(LOG_DEBUG, sql_command);

	  dbires = dbi_conn_query(conn, sql_command);
	  if (!dbires /*dbi_conn_error_flag(conn)*/) {
	    sprintf(sql_command, "250:%s\n", dbname);
	    if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
	      send_status(ptr_clrequest->fd, 801, TERM_NO);
	      LOG_PRINT(LOG_CRIT, get_status_msg(801));
	      free(sql_command);
	      return 1;
	    }
	    else {
	      ptr_addresult->msg = new_msg;
	    }
	  }
	  else {
	    dbi_result_free(dbires);
	  }
	}
      } /* end if pgsql */
    } /* end if other databases */
    
    if (error) {
      ptr_addresult->failure++;
    }
    else {
      sprintf(sql_command, "251:%s\n", dbname);
      if ((new_msg = mstrcat(ptr_addresult->msg, sql_command, &(ptr_addresult->msg_len), 0)) == NULL) {
	send_status(ptr_clrequest->fd, 801, TERM_NO);
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	free(sql_command);
	return 1;
      }
      else {
	ptr_addresult->msg = new_msg;
      }
      ptr_addresult->success++;
    }
  } /* end for */

  dbi_conn_close(conn);

  send_status(ptr_clrequest->fd, 0, TERM_NO);
  tiwrite(ptr_clrequest->fd, ptr_addresult->msg, TERM_YES);

  free(sql_command);
  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  adduser(): implements the client command adduser

  int adduser returns 1 if mem error, 2 if other error, 0 if successful

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  char *user_host string containing the name of the host from which
                  username will access the database. This is actually
                  the host on which the refdbd runs that username
                  will use to access the database. if user_host is
                  NULL, it will be set to localhost

  char *newuser_passwd string with the password for new users

  int n_remove if 0, access will be granted. if 1, access will be revoked

  int n_readonly if 0, user will have read/write access. If 1, user
                   will have read-only access

  struct ADDRESULT* ptr_addresult ptr to structure receiving the result

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int adduser(struct CLIENT_REQUEST* ptr_clrequest, char *user_host, char *newuser_passwd, int n_remove, int n_readonly, struct ADDRESULT* ptr_addresult) {
  dbi_conn conn;
  dbi_result dbires;
  int nread_done = 0;
  int inbuffer_len;
  int numbyte;
  int nhave_refdb;
  int n_host_regex = 0;
  int n_user_regex = 0;
  int n_client_status;
  size_t token_size;
  char *sql_command;
  char *inbuffer;
  char *new_inbuffer;
  char *token;
  char *userathost;
  char *new_msg;
  const char *item;
  const char *drivername;
  char localhost[] = "localhost";
  char message[128] = "";
  struct lilimem sentinel;

  sentinel.ptr_mem = NULL;
  sentinel.ptr_next = NULL;
  sentinel.varname[0] = '\0';

  sql_command = malloc(1024); 
  if (sql_command == NULL || insert_lilimem(&sentinel, (void**)&sql_command, NULL)) {
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }

  inbuffer_len = (size_t)COMMAND_INBUF_LEN;
  inbuffer = malloc(inbuffer_len);
  if (inbuffer == NULL || insert_lilimem(&sentinel, (void**)&inbuffer, NULL)) {
    delete_all_lilimem(&sentinel);
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }

  /* connect to the main database */
  if ((conn = connect_to_db(ptr_clrequest, NULL, 0)) != NULL) {
    /* see what driver we use */
    drivername = dbi_driver_get_name(dbi_conn_get_driver(conn));

    if (!strcmp(drivername, "sqlite")
	|| !strcmp(drivername, "sqlite3")) {
      /* driver does not support SQL-based access control */
      send_status(ptr_clrequest->fd, 224, TERM_NO);
      LOG_PRINT(LOG_WARNING, "999:access control not supported");
      dbi_conn_close(conn);
      delete_all_lilimem(&sentinel);
      return 1;
    }

    /* send back confirmation to the client */
    send_status(ptr_clrequest->fd, 0, TERM_NO);

    /* read from client and increase buffer size until we have all arguments */
    if ((n_client_status = read_status(ptr_clrequest->fd))) {
      delete_all_lilimem(&sentinel);
      LOG_PRINT(LOG_WARNING, get_status_msg(n_client_status));
      return 1;
    }

    while (!nread_done) {
      numbyte = tread(ptr_clrequest->fd, inbuffer, COMMAND_INBUF_LEN-1);
      if (get_trailz(inbuffer, numbyte) == TERM_LEN) { /* if transmission complete */
	nread_done = 1;
      }
      else {
	inbuffer_len += (size_t)COMMAND_INBUF_LEN;
	if ((new_inbuffer = (char*)realloc(inbuffer, inbuffer_len)) == NULL) {
	  delete_all_lilimem(&sentinel);
	  send_status(ptr_clrequest->fd, 801, TERM_NO);
	  LOG_PRINT(LOG_CRIT, get_status_msg(801));
	  dbi_conn_close(conn);
	  return 0;
	}
	else {
	  inbuffer = new_inbuffer;
	}
      }
    }


    /* +++++++++++++++++++++++++++++++++++++++++++++++++++++ */
    /* mysql */

    /* mysqld keeps authentication information in the mysql
       database. These data are used for all user databases.
       Therefore we need only one entry to allow access to
       the database server, and entries per database to
       allow access to the databases themselves */
    if (!strcmp(drivername, "mysql")) {
      int create_new = 0;
      char lockstring[64];
      struct VERSIONINFO ver;

      /* obtain database server version info */
      if (my_dbi_conn_get_versioninfo(conn, &ver)) {
	send_status(ptr_clrequest->fd, 205, TERM_NO);
	LOG_PRINT(LOG_ERR, get_status_msg(205));
	dbi_conn_close(conn);
	delete_all_lilimem(&sentinel);
	return 1;
      }

      /* MySQL 4.0.2 introduced new privileges "LOCK TABLES" and
	 "CREATE TEMPORARY TABLES". As older versions will barf at it,
	 add them only if we use a newer version */
      if (!n_readonly
	  && ((ver.major == 4 && ver.minor == 0 && ver.minuscule >=2)
	      || (ver.major == 4 && ver.minor > 0)
	      || ver.major > 4)) {
	strcpy(lockstring, ",LOCK TABLES, CREATE TEMPORARY TABLES");
      }
      else if (n_readonly
	       && ((ver.major == 4 && ver.minor == 0 && ver.minuscule >=2)
		   || (ver.major == 4 && ver.minor > 0)
		   || ver.major > 4)){
	strcpy(lockstring, ",LOCK TABLES ");
      }
      else {
	strcpy(lockstring, " ");
      }
	
      /* use localhost if no host was specified */
      if (!*user_host) {
	strcat(ptr_addresult->msg, "236\n");
	LOG_PRINT(LOG_INFO, get_status_msg(236));
	user_host = localhost;
      }

      /* select mysql database */
      if (dbi_conn_select_db(conn, "mysql") == -1) {
	send_status(ptr_clrequest->fd, 205, TERM_NO);
	LOG_PRINT(LOG_ERR, get_status_msg(205));
	dbi_conn_close(conn);
	delete_all_lilimem(&sentinel);
	return 1;
      }

      /* see whether the host info is a SQL regexp */
      if (strchr(user_host, (int)'%') || strchr(user_host, (int)'_')) {
	n_host_regex = 1;
      }
      else {
	n_host_regex = 0;
      }
      
      token = inbuffer;
      token_size = -1;

      userathost = malloc(USERNAME_LENGTH + HOSTNAME_LENGTH + 16); 
      if (userathost == NULL || insert_lilimem(&sentinel, (void**)&userathost, NULL)) {
	send_status(ptr_clrequest->fd, 801, TERM_NO);
	LOG_PRINT(LOG_CRIT, get_status_msg(801));
	return 1;
      }

      do {
	token = nstrtok(token + token_size + 1, &token_size, " ");
	if (token != NULL) {
	  token[token_size] = '\0';
	  
	  /* see whether the user info is a SQL regexp */
	  if (strchr(token, (int)'%') || strchr(token, (int)'_')) {
	    n_user_regex = 1;
	  }
	  else {
	    n_user_regex = 0;
	  }

	  if (n_remove) {
	    if (n_user_regex || n_host_regex) {
	      sprintf(sql_command, "REVOKE ALL ON %s.* FROM \'%s\'@\'%s\'", ptr_clrequest->current_db, token, user_host);
	    }
	    else {
	      sprintf(sql_command, "REVOKE ALL ON %s.* FROM %s@%s", ptr_clrequest->current_db, token, user_host);
	    }
	  }
	  else {
	    /* check whether the given user/host combo exists in User table */
	    sprintf(sql_command, "SELECT User FROM user WHERE User LIKE \'%s\' AND Host LIKE \'%s\'", token, user_host);
	    LOG_PRINT(LOG_DEBUG, sql_command);
	    dbires = dbi_conn_query(conn, sql_command);
	    if (!dbires) {
	      ptr_addresult->failure++;
	      LOG_PRINT(LOG_WARNING, get_status_msg(234));
	      snprintf(message, 128, "234:%s@%s:%s\n", token, user_host, ptr_clrequest->current_db); 
	      if ((new_msg = mstrcat(ptr_addresult->msg, message, &(ptr_addresult->msg_len), 0)) == NULL) {
		send_status(ptr_clrequest->fd, 801, TERM_NO);
		LOG_PRINT(LOG_CRIT, get_status_msg(801));
		return 1;
	      }
	      else {
		ptr_addresult->msg = new_msg;
	      }
	      continue; 
	    }

	    if (!dbi_result_get_numrows(dbires) && !n_user_regex) { /* user does not exist yet and does not contain regexp */
	      create_new++;
	    }

	    dbi_result_free(dbires);
	    
	    /* check whether this host/user combo has access rights to
	       refdb. If not, create as well */
	    /* reset variables */
	    nhave_refdb = 0;
	    sprintf(sql_command, "SELECT User,Db FROM db WHERE User=\'%s\' AND Host=\'%s\' AND Db=\'%s\'", token, user_host, main_db);
	    LOG_PRINT(LOG_DEBUG, sql_command);
	    dbires = dbi_conn_query(conn, sql_command);
	    if (!dbires) {
	      ptr_addresult->failure++;
	      LOG_PRINT(LOG_WARNING, get_status_msg(234));
	      snprintf(message, 128, "234:%s@%s:%s\n", token, user_host, ptr_clrequest->current_db); 
	      if ((new_msg = mstrcat(ptr_addresult->msg, message, &(ptr_addresult->msg_len), 0)) == NULL) {
		send_status(ptr_clrequest->fd, 801, TERM_NO);
		LOG_PRINT(LOG_CRIT, get_status_msg(801));
		return 1;
	      }
	      else {
		ptr_addresult->msg = new_msg;
	      }
	      continue; 
	    }

	    while (dbi_result_next_row(dbires)) {
	      item = dbi_result_get_string_idx(dbires, 2);
	      if (!strcmp(item, main_db)) {
		nhave_refdb = 1;
	      }
	    }

	    dbi_result_free(dbires);
	    
	    if (n_user_regex || n_host_regex) {
	      sprintf(userathost, "\'%s\'@\'%s\'", token, user_host);
	    }
	    else {
	      sprintf(userathost, "%s@%s", token, user_host);
	    }

	    if (!nhave_refdb || (newuser_passwd && *newuser_passwd)) { /* user does not have access rights to refdb */
	      if (newuser_passwd && *newuser_passwd) {
		/* if the user doesn't exist yet, this command will create
		   a new user with the given password. If the user already
		   exists, the password will be changed to the one given */
		if (!n_readonly) {
		  sprintf(sql_command, "GRANT SELECT, INSERT, UPDATE, DELETE%s ON %s.* TO %s IDENTIFIED BY \'%s\'", lockstring, main_db, userathost, newuser_passwd);
		}
		else {
		  sprintf(sql_command, "GRANT SELECT%s ON %s.* TO %s IDENTIFIED BY \'%s\'", lockstring, main_db, userathost, newuser_passwd);
		}
	      }
	      else if (create_new) {
		/* avoid possible security risk: we provide a default
		   password to avoid creating users with no passwords */
		if (!n_readonly) {
		  sprintf(sql_command, "GRANT SELECT, INSERT, UPDATE, DELETE%s ON %s.* TO %s IDENTIFIED BY \'refdb\'", lockstring, main_db, userathost);
		}
		else {
		  sprintf(sql_command, "GRANT SELECT%s ON %s.* TO %s IDENTIFIED BY \'refdb\'", lockstring, main_db, userathost);
		}
	      }
	      else {
		/* the user already exists and we don't touch the password */
		if (!n_readonly) {
		  sprintf(sql_command, "GRANT SELECT, INSERT, UPDATE, DELETE%s ON %s.* TO %s", lockstring, main_db, userathost);
		}
		else {
		  sprintf(sql_command, "GRANT SELECT%s ON %s.* TO %s", lockstring, main_db, userathost);
		}
	      }
	      LOG_PRINT(LOG_DEBUG, sql_command);
	      dbires = dbi_conn_query(conn, sql_command);
	      if (!dbires) {
		ptr_addresult->failure++;
		LOG_PRINT(LOG_WARNING, get_status_msg(223));
		snprintf(message, 128, "223:%s@%s:%s\n", token, user_host, ptr_clrequest->current_db); 
		if ((new_msg = mstrcat(ptr_addresult->msg, message, &(ptr_addresult->msg_len), 0)) == NULL) {
		  send_status(ptr_clrequest->fd, 801, TERM_NO);
		  LOG_PRINT(LOG_CRIT, get_status_msg(801));
		  return 1;
		}
		else {
		  ptr_addresult->msg = new_msg;
		}
		continue;
	      }
	      dbi_result_free(dbires);
	    }


	    /* now handle the requested database. Users need CREATE and 
	       DROP privileges for temp tables during bibliography creation*/
	    if (n_user_regex || n_host_regex) {
	      if (!n_readonly) {
		sprintf(sql_command, "GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP%s ON %s.* TO \'%s\'@\'%s\'", lockstring, ptr_clrequest->current_db, token, user_host);
	      }
	      else {
		sprintf(sql_command, "GRANT SELECT%s ON %s.* TO \'%s\'@\'%s\'", lockstring, ptr_clrequest->current_db, token, user_host);
	      }
	    }
	    else {
	      if (!n_readonly) {
		sprintf(sql_command, "GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP%s ON %s.* TO %s@%s", lockstring, ptr_clrequest->current_db, token, user_host);
	      }
	      else {
		sprintf(sql_command, "GRANT SELECT%s ON %s.* TO %s@%s", lockstring, ptr_clrequest->current_db, token, user_host);
	      }
	    }
	  }
	  LOG_PRINT(LOG_DEBUG, sql_command);
	  dbires = dbi_conn_query(conn, sql_command);
	  if (!dbires) {
	    ptr_addresult->failure++;
	    if (!n_remove) {
	      LOG_PRINT(LOG_WARNING, get_status_msg(223));
	      snprintf(message, 128, "223:%s@%s:%s\n", token, user_host, ptr_clrequest->current_db); 
	    }
	    else {
	      LOG_PRINT(LOG_WARNING, get_status_msg(239));
	      snprintf(message, 128, "239:%s@%s:%s\n", token, user_host, ptr_clrequest->current_db); 
	    }
	  }
	  else {
	    ptr_addresult->success++;
	    dbi_result_free(dbires);
	    if (!n_remove) {
	      if (user_host && *user_host) {
		snprintf(message, 127, "237:%s@%s:%s", token, user_host, ptr_clrequest->current_db); 
	      }
	      else {
		snprintf(message, 127, "237:%s:%s", token, ptr_clrequest->current_db); 
	      }
	      LOG_PRINT(LOG_INFO, message);
	      strcat(message, "\n");
	    }
	    else {
	      if (user_host && *user_host) {
		snprintf(message, 127, "238:%s@%s:%s", token, user_host, ptr_clrequest->current_db); 
	      }
	      else {
		snprintf(message, 127, "238:%s:%s", token, ptr_clrequest->current_db); 
	      }
	      LOG_PRINT(LOG_INFO, message);
	      strcat(message, "\n");
	    }
	  }

	  if ((new_msg = mstrcat(ptr_addresult->msg, message, &(ptr_addresult->msg_len), 0)) == NULL) {
	    send_status(ptr_clrequest->fd, 801, TERM_NO);
	    LOG_PRINT(LOG_CRIT, get_status_msg(801));
	    return 1;
	  }
	  else {
	    ptr_addresult->msg = new_msg;
	  }
	}
      } while (token != NULL);

      /* reloading permission tables is not necessary when using 
	 the GRANT/REVOKE syntax. Thus quoth the manual, but seeing
	 is believing */

      strcpy(sql_command, "FLUSH PRIVILEGES");
      LOG_PRINT(LOG_DEBUG, sql_command);
      dbires = dbi_conn_query(conn, sql_command);
    }

    /* +++++++++++++++++++++++++++++++++++++++++++++++++++++ */
    /* pgsql */
    else if (!strcmp(drivername, "pgsql")) {
      /* PostgreSQL knows users and groups. Access is granted to
	 tables (and other stuff we don't use here). The system tables
	 that hold the user and group information are accessible
	 through connections to any existing database (at least for
	 the superuser, that is).  Each new refdb database creates a
	 read/write group and a read-only group and grants access for
	 these groups. We simply add users to or remove them from
	 these groups.  Same for the groups refdbuser/refdbruser which
	 allow access to the common database refdb
       */

      token = inbuffer;
      token_size = -1;

      /* loop over all usernames */
      /* ToDo: this loop is inefficient. Better construct a comma-separated
	 list of users and pass this as an argument to a single ALTER GROUP
	 command */
      do {
	token = nstrtok(token + token_size + 1, &token_size, " ");
	if (token != NULL) {
	  token[token_size] = '\0';
	  
	  if (n_remove) {
	    /* the current implementation removes only the group membership
	       for the given database. Users will still have access to the
	       refdb database. */
	    /* ToDo: maybe a client command switch can be used to force
	       removal from group refdbuser */
	    if (!n_readonly) {
	      sprintf(sql_command, "ALTER GROUP %suser DROP USER %s", ptr_clrequest->current_db, token);
	    }
	    else {
	      sprintf(sql_command, "ALTER GROUP %sruser DROP USER %s", ptr_clrequest->current_db, token);
	    }
	    LOG_PRINT(LOG_DEBUG, sql_command);
	    dbires = dbi_conn_query(conn, sql_command);
	    if (!dbires) {
	      ptr_addresult->failure++;
	      LOG_PRINT(LOG_WARNING, get_status_msg(239));
	      snprintf(message, 128, "239:%s@%s:%s\n", token, user_host, ptr_clrequest->current_db); 
	    }
	    else {
	      ptr_addresult->success++;
	      dbi_result_free(dbires);
	      LOG_PRINT(LOG_WARNING, get_status_msg(238));
	      if (user_host && *user_host) {
		snprintf(message, 128, "238:%s@%s:%s\n", token, user_host, ptr_clrequest->current_db); 
	      }
	      else {
		snprintf(message, 128, "238:%s:%s\n", token, ptr_clrequest->current_db); 
	      }
	    }
	    if ((new_msg = mstrcat(ptr_addresult->msg, message, &(ptr_addresult->msg_len), 0)) == NULL) {
	      send_status(ptr_clrequest->fd, 801, TERM_NO);
	      LOG_PRINT(LOG_CRIT, get_status_msg(801));
	      return 1;
	    }
	    else {
	      ptr_addresult->msg = new_msg;
	    }
	  }
	  else { /* add new user */
	    if (is_user_pgsql(conn, token) != 1) {
	      sprintf(sql_command, "CREATE USER %s WITH PASSWORD \'%s\'", token, (*newuser_passwd) ? newuser_passwd : "refdb");
	      LOG_PRINT(LOG_DEBUG, sql_command);
	      dbires = dbi_conn_query(conn, sql_command);
	      if (!dbires) {
		ptr_addresult->failure++;
		LOG_PRINT(LOG_WARNING, get_status_msg(223));
		snprintf(message, 128, "223:%s@%s:%s\n", token, user_host, ptr_clrequest->current_db); 
		if ((new_msg = mstrcat(ptr_addresult->msg, message, &(ptr_addresult->msg_len), 0)) == NULL) {
		  send_status(ptr_clrequest->fd, 801, TERM_NO);
		  LOG_PRINT(LOG_CRIT, get_status_msg(801));
		  return 1;
		}
		else {
		  ptr_addresult->msg = new_msg;
		}
		continue;
	      }
	      dbi_result_free(dbires);
	    }

	    /* Add user to the group refdbuser */
	    /* Adding users that already are members will not cause
	       an error. Checking membership would be extremely tedious as
	       the user list is implemented as an array instead of
	       using a separate table */
	    if (!n_readonly) {
	      sprintf(sql_command, "ALTER GROUP %suser ADD USER %s", main_db, token);
	    }
	    else {
	      sprintf(sql_command, "ALTER GROUP %sruser ADD USER %s", main_db, token);
	    }
	    LOG_PRINT(LOG_DEBUG, sql_command);
	    dbires = dbi_conn_query(conn, sql_command);
	    if (!dbires) {
	      ptr_addresult->failure++;
	      LOG_PRINT(LOG_WARNING, get_status_msg(223));
	      snprintf(message, 128, "223:%s@%s:%s\n", token, user_host, ptr_clrequest->current_db); 
	      if ((new_msg = mstrcat(ptr_addresult->msg, message, &(ptr_addresult->msg_len), 0)) == NULL) {
		send_status(ptr_clrequest->fd, 801, TERM_NO);
		LOG_PRINT(LOG_CRIT, get_status_msg(801));
		return 1;
	      }
	      else {
		ptr_addresult->msg = new_msg;
	      }
	      continue;
	    }
	    dbi_result_free(dbires);

	    /* now handle the requested database and add the user
	       to the group <dbname>user */
	    if (!n_readonly) {
	      sprintf(sql_command, "ALTER GROUP %suser ADD USER %s", ptr_clrequest->current_db, token);
	    }
	    else {
	      sprintf(sql_command, "ALTER GROUP %sruser ADD USER %s", ptr_clrequest->current_db, token);
	    }
	    LOG_PRINT(LOG_DEBUG, sql_command);
	    dbires = dbi_conn_query(conn, sql_command);
	    if (!dbires) {
	      ptr_addresult->failure++;
	      LOG_PRINT(LOG_WARNING, get_status_msg(239));
	      snprintf(message, 128, "239:%s@%s:%s\n", token, user_host, ptr_clrequest->current_db); 
	    }
	    else {
	      ptr_addresult->success++;
	      dbi_result_free(dbires);
	      if (user_host && *user_host) {
		snprintf(message, 127, "238:%s@%s:%s", token, user_host, ptr_clrequest->current_db); 
	      }
	      else {
		snprintf(message, 127, "238:%s:%s", token, ptr_clrequest->current_db); 
	      }
	      LOG_PRINT(LOG_INFO, message);
	      strcat(message, "\n");
	    }

	    if ((new_msg = mstrcat(ptr_addresult->msg, message, &(ptr_addresult->msg_len), 0)) == NULL) {
	      send_status(ptr_clrequest->fd, 801, TERM_NO);
	      LOG_PRINT(LOG_CRIT, get_status_msg(801));
	      return 1;
	    }
	    else {
	      ptr_addresult->msg = new_msg;
	    }
	  } /* end if n_remove */
 	} /* end if token != NULL */
      } while (token != NULL); /* end loop over all usernames */
    }
    dbi_conn_close(conn);
  }
  else {
    send_status(ptr_clrequest->fd, 202, TERM_NO);
    LOG_PRINT(LOG_WARNING, get_status_msg(202));
    return 1;
  }
  
  send_status(ptr_clrequest->fd, 0, TERM_NO);
  tiwrite(ptr_clrequest->fd, ptr_addresult->msg, TERM_YES);
  delete_all_lilimem(&sentinel);
  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  addword(): implements the client command addword

  int addword returns >0 if failed, 0 if successful

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  int n_remove if 0, access wil be granted. if 1, access will be revoked

  struct ADDRESULT* ptr_addresult ptr to struct with counters

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int addword(struct CLIENT_REQUEST* ptr_clrequest, int n_remove, struct ADDRESULT* ptr_addresult) {
  dbi_conn conn;
  dbi_result dbires;
  dbi_driver driver;
  int nread_done = 0;
  int inbuffer_len;
  size_t token_size;
  int numbyte;
  int n_client_status;
  char message[512];
  char* new_msg;
  char* sql_command;
  char* inbuffer;
  char* new_inbuffer;
  char* token;
  char* mytoken;
  struct lilimem sentinel;

  sentinel.ptr_mem = NULL;
  sentinel.ptr_next = NULL;
  sentinel.varname[0] = '\0';


  sql_command = malloc(1024); 
  if (sql_command == NULL || insert_lilimem(&sentinel, (void**)&sql_command, NULL)) {
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }

  inbuffer_len = (size_t)COMMAND_INBUF_LEN;
  inbuffer = malloc(inbuffer_len);
  if (inbuffer == NULL || insert_lilimem(&sentinel, (void**)&inbuffer, NULL)) {
    delete_all_lilimem(&sentinel);
    send_status(ptr_clrequest->fd, 801, TERM_NO);
    LOG_PRINT(LOG_CRIT, get_status_msg(801));
    return 1;
  }

  /* connect to the database */
  if ((conn = connect_to_db(ptr_clrequest, NULL, 0)) != NULL) {

    send_status(ptr_clrequest->fd, 0, TERM_NO);

    /* read from client and increase buffer size until we have all arguments */
    if ((n_client_status = read_status(ptr_clrequest->fd))) {
      delete_all_lilimem(&sentinel);
      LOG_PRINT(LOG_WARNING, get_status_msg(n_client_status));
      return 1;
    }

    while (!nread_done) {
      numbyte = tread(ptr_clrequest->fd, inbuffer, COMMAND_INBUF_LEN-1);
      if (get_trailz(inbuffer, numbyte) >= TERM_LEN) { /* if transmission complete */
	nread_done = 1;
      }
      else {
	inbuffer_len += (size_t)COMMAND_INBUF_LEN;
	if ((new_inbuffer = (char*)realloc(inbuffer, inbuffer_len)) == NULL) {
	  delete_all_lilimem(&sentinel);
	  dbi_conn_close(conn);
	  send_status(ptr_clrequest->fd, 801, TERM_NO);
	  LOG_PRINT(LOG_CRIT, get_status_msg(801));
	  return 1;
	}
	else {
	  inbuffer = new_inbuffer;
	}
      }
    } /* end while(!nread_done) */

/*      printf("%s\n", inbuffer); */

    token = inbuffer;
    token_size = -1;

    do {
      token = nstrtok(token + token_size + 1, &token_size, " ");
      if (token != NULL) {
	token[token_size] = '\0';

	/* make sure we have uppercase words only */
	mytoken = strdup(token);
	if (!mytoken) {
	  ptr_addresult->failure++;
	  send_status(ptr_clrequest->fd, 801, TERM_NO);
	  LOG_PRINT(LOG_CRIT, get_status_msg(801));
	  return 1;
	}
	strup(mytoken);

	driver = dbi_conn_get_driver(conn);

	/* escape any characters that the database server cannot digest */
	if (dbi_conn_quote_string(conn, &mytoken) == 0) {
	  free(mytoken);
	  ptr_addresult->failure++;
	  send_status(ptr_clrequest->fd, 801, TERM_NO);
	  LOG_PRINT(LOG_CRIT, get_status_msg(801));
	  return 1;
	}

	if (n_remove) {
  	  sprintf(sql_command, "DELETE FROM t_journal_words WHERE name %s %s", my_dbi_conn_get_cap(conn, "rlike"), mytoken);
	}
	else {
	  sprintf(sql_command, "INSERT INTO t_journal_words (name) VALUES (%s)", mytoken);
	}
	LOG_PRINT(LOG_DEBUG, sql_command);
	free(mytoken);

	dbires = dbi_conn_query(conn, sql_command);
	if (!dbires || !dbi_result_get_numrows_affected(dbires)) {
	  if (n_remove) {
	    sprintf(message, "420:%s\n", token);
	  }
	  else {
	    sprintf(message, "414:%s\n", token);
	  }

	  if ((new_msg = mstrcat(ptr_addresult->msg, message, &(ptr_addresult->msg_len), 0)) == NULL) {
	    send_status(ptr_clrequest->fd, 801, TERM_NO);
	    LOG_PRINT(LOG_CRIT, get_status_msg(801));
	    return 1;
	  }
	  else {
	    ptr_addresult->msg = new_msg;
	  }
	  ptr_addresult->failure++;
	}
	else {
	  dbi_result_free(dbires);
	  if (n_remove) {
	    sprintf(message, "419:%s\n", token);
	  }
	  else {
	    sprintf(message, "408:%s\n", token);
	  }

	  if ((new_msg = mstrcat(ptr_addresult->msg, message, &(ptr_addresult->msg_len), 0)) == NULL) {
	    send_status(ptr_clrequest->fd, 801, TERM_NO);
	    LOG_PRINT(LOG_CRIT, get_status_msg(801));
	    return 1;
	  }
	  else {
	    ptr_addresult->msg = new_msg;
	  }
	  ptr_addresult->success++;
	}
      }
    } while (token != NULL);
    dbi_conn_close(conn);
  }
  else {
    /* send back error to the client */
    send_status(ptr_clrequest->fd, 202, TERM_NO);
    LOG_PRINT(LOG_WARNING, get_status_msg(202));
    delete_all_lilimem(&sentinel);
    return 1;
  }

  /* send back report to client */
  send_status(ptr_clrequest->fd, 0, TERM_NO);
  tiwrite(ptr_clrequest->fd, ptr_addresult->msg, TERM_YES);

  delete_all_lilimem(&sentinel);
  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  getfoo(): implements the client commands getau, getkw, and getjo

  int getfoo returns 0 if ok, > 0 if error

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  int type the type of user command 0 = getau, 1 = getkw, 2 = getjo,
                                    3 = getjf, 4 = getj1, 5 = getj2
				    6 = geted, 7 = getas, 8 = getax

  int all if 1, return all synonyms of a journal name query

  char* db_encoding output encoding

  struct ADDRESULT* ptr_addresult ptr to struct that receives counts

  char* format_string ptr to string containing formatting hints

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int getfoo(struct CLIENT_REQUEST* ptr_clrequest, int type, int all, char* db_encoding, struct ADDRESULT* ptr_addresult, char* format_string) {
  int n_sql_command_len;
  int rel_frequency;
  int n_leadingspaces;
  unsigned long long refcount;
  unsigned long long frequency;
  unsigned long long periodical_id;
  char buffer[512];
  char limitstring[128] = "";
  char *sql_command;
  char *myarg;
  char spaces[] = "                   "; /* ulonglong is 19 digits max */
  char* leadingspace;
  const char *foostring;
  dbi_conn conn;
  dbi_result dbires;
  dbi_result dbires1;
  dbi_driver driver;

  /* connect to the database */
  if ((conn = connect_to_db(ptr_clrequest, NULL, 0)) != NULL) {
    driver = dbi_conn_get_driver(conn);

    /* get reference count for later use */
    refcount = get_reference_count(conn, NULL, 0 /* istemp */);

    if (ptr_clrequest->argument && *(ptr_clrequest->argument)) {
      myarg = malloc(strlen(ptr_clrequest->argument)+1);
      unescape_chars(myarg, ptr_clrequest->argument, strlen(ptr_clrequest->argument));
    }
    else {
      myarg = strdup(my_dbi_driver_get_cap(driver, "listall")); /* list all if no argument is specified */
    }
    if (!myarg) {
      send_status(ptr_clrequest->fd, 801, TERM_NO);
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      dbi_conn_close(conn);
      return 1;
    }

    /* escape any characters that the database server cannot digest */
    if (dbi_conn_quote_string(conn, &myarg) == 0) {
      send_status(ptr_clrequest->fd, 801, TERM_NO);
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      dbi_conn_close(conn);
      free(myarg);
      return 1;
    }

    n_sql_command_len = 512+strlen(myarg);
    sql_command = malloc(n_sql_command_len); 
    if (sql_command == NULL) {
      send_status(ptr_clrequest->fd, 801, TERM_NO);
      LOG_PRINT(LOG_CRIT, get_status_msg(801));
      dbi_conn_close(conn);
      free(myarg);
      return 1;
    }

    if (*(ptr_clrequest->limit)) {
      char *colon;

      colon = strchr(ptr_clrequest->limit, (int)':');
      
      if (!colon) {
	snprintf(limitstring, 128, " LIMIT %s ", ptr_clrequest->limit);
      }
      else {
	*colon = '\0';
	snprintf(limitstring, 128, " LIMIT %s OFFSET %s ", ptr_clrequest->limit, colon+1);
      }
    }

    /* assemble query */
    if (type == GETAU) {
      sprintf(sql_command, "SELECT DISTINCT t_author.author_name, t_author.author_lastname, t_author.author_firstname, t_author.author_middlename, t_author.author_suffix, t_xauthor.xauthor_role, t_xauthor.author_id FROM t_author INNER JOIN t_xauthor ON t_author.author_id=t_xauthor.author_id WHERE t_xauthor.xauthor_type=%s AND t_author.author_name %s %s ORDER BY t_author.author_name%s", get_author_type_string(driver, 1), my_dbi_conn_get_cap(conn, "rlike"), myarg, limitstring);
    }
    else if (type == GETKW) {
      sprintf(sql_command, "SELECT DISTINCT keyword_name, keyword_id FROM t_keyword WHERE keyword_name %s %s ORDER BY keyword_name%s", my_dbi_conn_get_cap(conn, "rlike"), myarg, limitstring);
    }
    else if (type == GETJO) {
      sprintf(sql_command, "SELECT DISTINCT periodical_abbrev, periodical_id, periodical_name, periodical_custabbrev1, periodical_custabbrev2 FROM t_periodical WHERE periodical_abbrev %s %s ORDER BY periodical_abbrev,periodical_name,periodical_custabbrev1,periodical_custabbrev2%s", my_dbi_conn_get_cap(conn, "rlike"), myarg, limitstring);
    }
    else if (type == GETJF) {
      sprintf(sql_command, "SELECT DISTINCT periodical_name, periodical_id, periodical_abbrev, periodical_custabbrev1, periodical_custabbrev2 FROM t_periodical WHERE periodical_name %s %s ORDER BY periodical_abbrev,periodical_name,periodical_custabbrev1,periodical_custabbrev2%s", my_dbi_conn_get_cap(conn, "rlike"), myarg, limitstring);
    }
    else if (type == GETJ1) {
      sprintf(sql_command, "SELECT DISTINCT periodical_custabbrev1, periodical_id, periodical_name, periodical_abbrev, periodical_custabbrev2 FROM t_periodical WHERE periodical_custabbrev1 %s %s ORDER BY periodical_abbrev,periodical_name,periodical_custabbrev1,periodical_custabbrev2%s", my_dbi_conn_get_cap(conn, "rlike"), myarg, limitstring);
    }
    else if (type == GETJ2) {
      sprintf(sql_command, "SELECT DISTINCT periodical_custabbrev2, periodical_id, periodical_name, periodical_abbrev, periodical_custabbrev1 FROM t_periodical WHERE periodical_custabbrev2 %s %s ORDER BY periodical_abbrev,periodical_name,periodical_custabbrev1,periodical_custabbrev2%s", my_dbi_conn_get_cap(conn, "rlike"), myarg, limitstring);
    }
    else if (type == GETED) {
      sprintf(sql_command, "SELECT DISTINCT t_author.author_name, t_author.author_lastname, t_author.author_firstname, t_author.author_middlename, t_author.author_suffix, t_xauthor.xauthor_role, t_xauthor.author_id FROM t_author, t_xauthor WHERE t_author.author_id=t_xauthor.author_id AND t_xauthor.xauthor_type=%s AND t_author.author_name %s %s ORDER BY t_author.author_name%s", get_author_type_string(driver, 2), my_dbi_conn_get_cap(conn, "rlike"), myarg, limitstring);
    }
    else if (type == GETAS) {
      sprintf(sql_command, "SELECT DISTINCT t_author.author_name, t_author.author_lastname, t_author.author_firstname, t_author.author_middlename, t_author.author_suffix, t_xauthor.xauthor_role, t_xauthor.author_id FROM t_author, t_xauthor WHERE t_author.author_id=t_xauthor.author_id AND t_xauthor.xauthor_type=%s AND t_author.author_name %s %s ORDER BY t_author.author_name%s", get_author_type_string(driver, 3), my_dbi_conn_get_cap(conn, "rlike"), myarg, limitstring);
    }
    else if (type == GETAX) {
      sprintf(sql_command, "SELECT DISTINCT t_author.author_name, t_author.author_lastname, t_author.author_firstname, t_author.author_middlename, t_author.author_suffix, t_xauthor.xauthor_role, t_xauthor.author_id FROM t_author, t_xauthor WHERE t_author.author_id=t_xauthor.author_id AND t_author.author_name %s %s ORDER BY t_author.author_name%s", my_dbi_conn_get_cap(conn, "rlike"), myarg, limitstring);
    }

    free(myarg);

    /* send query */
    LOG_PRINT(LOG_DEBUG, sql_command);
    dbires = dbi_conn_query(conn, sql_command);
    if (!dbires) {
      send_status(ptr_clrequest->fd, 234, TERM_NO);
      LOG_PRINT(LOG_WARNING, get_status_msg(234));
      dbi_conn_close(conn);
      free(sql_command);
      return 1;
    }

    free(sql_command);

    /* send results to client */
    send_status(ptr_clrequest->fd, 0, TERM_NO);

    /* see whether frequency data were requested */
    if (format_string
	&& (!strcmp(format_string, "freq")
	    || !strcmp(format_string, "relfreq"))) {
      if (type == GETAU
	  || type == GETED
	  || type == GETAS
	  || type == GETAX) {
	while ((foostring = get_extended_author(dbires, 0 /* is_temp */, &frequency)) != NULL) {
	  /* compute the leading space required to align the output */
	  n_leadingspaces = (refcount) ? (int)log10(refcount):0;
	  leadingspace = spaces+19-n_leadingspaces+((frequency) ? (int)log10(frequency):0);

	  ptr_addresult->success++;
	  if (!strcmp(format_string,"relfreq")) {
	    rel_frequency = calculate_relative_frequency(frequency, refcount);
	    sprintf(buffer, "%d:%s\n", rel_frequency, foostring);
	  }
	  else {
	    sprintf(buffer, "%s"ULLSPEC":%s\n", leadingspace, (unsigned long long)frequency, foostring);
	  }
	  tiwrite(ptr_clrequest->fd, buffer, TERM_NO);
	}
      }
      else if (type == GETKW) {
	while ((foostring = get_extended_keyword(dbires, 0 /* is_temp */, &frequency)) != NULL) {
	  /* compute the leading space required to align the output */
	  n_leadingspaces = (refcount) ? (int)log10(refcount):0;
	  leadingspace = spaces+19-n_leadingspaces+((frequency) ? (int)log10(frequency):0);

	  ptr_addresult->success++;
	  if (!strcmp(format_string,"relfreq")) {
	    rel_frequency = calculate_relative_frequency(frequency, refcount);
	    sprintf(buffer, "%d:%s\n", rel_frequency, foostring);
	  }
	  else {
	    sprintf(buffer, "%s"ULLSPEC":%s\n", leadingspace, (unsigned long long)frequency, foostring);
	  }
	  tiwrite(ptr_clrequest->fd, buffer, TERM_NO);
	}
      }
      else /* if (type == GETJX) */ {
	while (dbi_result_next_row(dbires)) {
	  ptr_addresult->success++;
	
	  periodical_id = my_dbi_result_get_idval_idx(dbires, 2);
      
	  sprintf(buffer, "SELECT count(*) FROM t_refdb WHERE t_refdb.refdb_periodical_id="ULLSPEC, (unsigned long long)periodical_id);

	  LOG_PRINT(LOG_DEBUG, buffer);

	  dbires1 = dbi_conn_query(dbi_result_get_conn(dbires), buffer);
	  if (!dbires1) {
	    continue;
	  }

	  if (dbi_result_next_row(dbires1)) {
	    frequency = my_dbi_result_get_idval_idx(dbires1, 1);
	    if (dbi_conn_error_flag(dbi_result_get_conn(dbires))) {
	      dbi_result_free(dbires1);
	      continue;
	    }
	    dbi_result_free(dbires1);
	  }
	  else {
	    dbi_result_free(dbires1);
	    frequency = 0;
	  }


	  /* compute the leading space required to align the output */
	  n_leadingspaces = (refcount) ? (int)log10(refcount):0;
	  leadingspace = spaces+19-n_leadingspaces+((frequency) ? (int)log10(frequency):0);

	  if (!all) {
	    foostring = dbi_result_get_string_idx(dbires, 1); /* 1-base index */
	    if (foostring) {
	      if (!strcmp(format_string,"relfreq")) {
		rel_frequency = calculate_relative_frequency(frequency, refcount);
		sprintf(buffer, "%d:%s\n", rel_frequency, foostring);
	      }
	      else {
		sprintf(buffer, "%s"ULLSPEC":%s\n", leadingspace, (unsigned long long)frequency, foostring);
	      }
	      tiwrite(ptr_clrequest->fd, buffer, TERM_NO);
	    }
	  }
	  else {
	    /* display all synonyms. If one is missing, we still print
	       the colon to indicate which one is missing */
	    if (!strcmp(format_string,"relfreq")) {
	      rel_frequency = calculate_relative_frequency(frequency, refcount);
	      sprintf(buffer, "%d:\n", rel_frequency);
	    }
	    else {
	      sprintf(buffer, ULLSPEC":\n", (unsigned long long)frequency);
	    }
	    tiwrite(ptr_clrequest->fd, buffer, TERM_NO);

	    foostring = dbi_result_get_string_idx(dbires, 1); /* 1-base index */
	    if (foostring) {
	      tiwrite(ptr_clrequest->fd, foostring, TERM_NO);
	    }
	  
	    tiwrite(ptr_clrequest->fd, ":", TERM_NO);
	
	    foostring = dbi_result_get_string_idx(dbires, 3); /* 1-base index */
	    if (foostring) {
	      tiwrite(ptr_clrequest->fd, foostring, TERM_NO);
	    }
	  
	    tiwrite(ptr_clrequest->fd, ":", TERM_NO);
	  
	    foostring = dbi_result_get_string_idx(dbires, 4); /* 1-base index */
	    if (foostring) {
	      tiwrite(ptr_clrequest->fd, foostring, TERM_NO);
	    }
	  
	    tiwrite(ptr_clrequest->fd, ":", TERM_NO);
	    
	    foostring = dbi_result_get_string_idx(dbires, 5); /* 1-base index */
	    if (foostring) {
	      tiwrite(ptr_clrequest->fd, foostring, TERM_NO);
	    }
	  }
	} /* end while */
      }
    }
    else { /* no frequency data requested */
      while (dbi_result_next_row(dbires)) {
	ptr_addresult->success++;
	
	if (!all) {
	  foostring = dbi_result_get_string_idx(dbires, 1); /* 1-base index */
	  if (foostring) {
	    tiwrite(ptr_clrequest->fd, foostring, TERM_NO);
	  }
	}
	else {
	  /* display all synonyms. If one is missing, we still print
	     the colon to indicate which one is missing */
	  foostring = dbi_result_get_string_idx(dbires, 1); /* 1-base index */
	  if (foostring) {
	    tiwrite(ptr_clrequest->fd, foostring, TERM_NO);
	  }
	  
	  tiwrite(ptr_clrequest->fd, ":", TERM_NO);
	
	  foostring = dbi_result_get_string_idx(dbires, 3); /* 1-base index */
	  if (foostring) {
	    tiwrite(ptr_clrequest->fd, foostring, TERM_NO);
	  }
	  
	  tiwrite(ptr_clrequest->fd, ":", TERM_NO);
	  
	  foostring = dbi_result_get_string_idx(dbires, 4); /* 1-base index */
	  if (foostring) {
	    tiwrite(ptr_clrequest->fd, foostring, TERM_NO);
	  }
	  
	  tiwrite(ptr_clrequest->fd, ":", TERM_NO);
	  
	  foostring = dbi_result_get_string_idx(dbires, 5); /* 1-base index */
	  if (foostring) {
	    tiwrite(ptr_clrequest->fd, foostring, TERM_NO);
	  }
	  tiwrite(ptr_clrequest->fd, "|", TERM_NO);
	}
	tiwrite(ptr_clrequest->fd, "\n", TERM_NO);
      } /* end while */
    } /* end if */

    /* terminate output */
    iwrite(ptr_clrequest->fd, cs_term, TERM_LEN);

    dbi_result_free(dbires);
    dbi_conn_close(conn);
  }
  else {
    /* connection failed */
    send_status(ptr_clrequest->fd, 204, TERM_NO);
    LOG_PRINT(LOG_WARNING, get_status_msg(204));
    return 1;
  }

  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  is_refdb_admin(): tests whether a user is likely to be a refdbd
                    administrator. We test whether the user has read
		    access to a system table readable only for
		    database administrators
		     
  int is_refdb_admin returns 0 if access is denied. returns 1 if
               access is allowed. If a database error occurs other
	       than denial of access, it will also return 0.
	       If the database engine does not support access control,
	       this function always returns 1
	       returns -1 if there was a connection problem

  struct CLIENT_REQUEST* ptr_clrequest ptr to structure with client info

  struct ADDRESULT* ptr_addresult ptr to struct with counters

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int is_refdb_admin(struct CLIENT_REQUEST* ptr_clrequest, struct ADDRESULT* ptr_addresult) {
  dbi_conn conn;
  dbi_result dbires;
  const char *drivername;

  /* connect to database server */
  if ((conn = connect_to_db(ptr_clrequest, NULL, 0)) == NULL) {
    return -1;
  }

  drivername = dbi_driver_get_name(dbi_conn_get_driver(conn));

  /* try to access a system table that only db administrators
     may access */
  if (!strcmp(drivername, "mysql")) {
    dbires = dbi_conn_query(conn, "SELECT User FROM mysql.user");
  }
  else if (!strcmp(drivername, "pgsql")) {
    dbires = dbi_conn_query(conn, "SELECT * FROM pg_shadow");
  }
  else {
    /* no access control */
    sprintf(ptr_addresult->msg, "704\n");
    dbi_conn_close(conn);
    return 1;
  }

  if (!dbires) {
    sprintf(ptr_addresult->msg, "705:%s", ptr_clrequest->username);
    LOG_PRINT(LOG_WARNING, ptr_addresult->msg);
    sprintf(ptr_addresult->msg, "705:%s\n", ptr_clrequest->username);
    dbi_conn_close(conn);
    return 0;
  }

  dbi_result_free(dbires);
  dbi_conn_close(conn);
  sprintf(ptr_addresult->msg, "705:%s\n", ptr_clrequest->username);

  return 1;
}


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  daemonize(): puts the process properly into the background 

  int daemonize returns 1 in case of an error, 0 if all went fine

  char* progname optional string containing the name of the program

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int daemonize(char* progname) {
  /* this whole fn is more or less stolen from the libslack package */
  /* libslack - http://libslack.org/ */
  /* Copyright (C) 1999-2001 raf <raf@raf.org> (GPL) */


  pid_t pid;
  long nopen;
  int fd;

  /*
  ** Don't setup a daemon-friendly process context
  ** if started by init(8) or inetd(8).
  */

  if (!(daemon_started_by_init() || daemon_started_by_inetd())) {
      /*
      ** Background the process.
      ** Lose process group leadership.
      */

    if ((pid = fork()) == -1) {
      return 1;
    }

    if (pid) {
      exit(0);
    }

    /* Become a process session leader. */

    setsid();

#ifndef NO_EXTRA_SVR4_FORK
#ifdef SVR4
    /*
    ** Lose process session leadership
    ** to prevent gaining a controlling
    ** terminal in SVR4.
    */

    if ((pid = fork()) == -1) {
      return 1;
    }

    if (pid) {
      exit(0);
    }
#endif
#endif
  }

  /* Enter the root directory to prevent hampering umounts. */

  if (chdir(ROOT_DIR) == -1) {
    return 1;
  }

  /* Clear umask to enable explicit file modes. */

  umask(0);

  /*
  ** We need to close all open file descriptors. Check how
  ** many file descriptors we have (If indefinite, a usable
  ** number (1024) will be returned).
  */

  if ((nopen = limit_open()) == -1) {
    return 1;
  }

  /*
  ** Close all open file descriptors. If started by inetd,
  ** we don't close stdin, stdout and stderr.
  ** Don't forget to open any future tty devices with O_NOCTTY
  ** so as to prevent gaining a controlling terminal
  ** (not necessary with SVR4).
  */

  if (daemon_started_by_inetd()) {
    for (fd = 0; fd < nopen; ++fd) {
      switch (fd) {
      case STDIN_FILENO:
      case STDOUT_FILENO:
      case STDERR_FILENO:
	break;
      default:
	close(fd);
      }
    }
  }
  else {
    for (fd = 0; fd < nopen; ++fd) {
      close(fd);
    }

    /*
    ** Open stdin, stdout and stderr to /dev/null just in case some
    ** code buried in a library somewhere expects them to be open.
    */

    if ((fd = open("/dev/null", O_RDWR)) == -1) {
      return 1;
    }

    /*
    ** This is only needed for very strange (hypothetical)
    ** POSIX implementations where STDIN_FILENO != 0 or
    ** STDOUT_FILE != 1 or STERR_FILENO != 2 (yeah, right).
    */

    if (fd != STDIN_FILENO) {
      if (dup2(fd, STDIN_FILENO) == -1) {
	return 1;
      }

      close(fd);
    }
    
    if (dup2(STDIN_FILENO, STDOUT_FILENO) == -1) {
      return 1;
    }

    if (dup2(STDIN_FILENO, STDERR_FILENO) == -1) {
      return 1;
    }
  }

  /* ToDo: deal with this later */
  /* Place our process id in the file system and lock it. */

/*    if (name) */
/*      { */
/*        int rc; */

/*        ptry(pthread_mutex_lock(&g.lock)) */
/*  	rc = daemon_pidfile(name); */
/*        ptry(pthread_mutex_unlock(&g.lock)) */

/*  	return rc; */
/*      } */

  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  daemon_started_by_init(): returns true if the daemon was started
                            by init(8)

  static int daemon_started_by_init returns 1 if the daemon was started by init
                             returns 0 if the daemon was started by
                             any other means

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int daemon_started_by_init(void) {
  /* this code also taken from libslack */
/*    static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; */
  static int rc = -1;

  if (rc == -1) {
/*        ptry(pthread_mutex_lock(&lock)) */

    if (rc == -1) {
      rc = (getppid() == 1);
    }

/*        ptry(pthread_mutex_unlock(&lock)) */
  }

  return rc;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  daemon_started_by_inetd(): returns true if the daemon was started
                             by inetd(8)

  static int daemon_started_by_inetd returns 1 if the daemon was started by
                             intetd. returns 0 otherwise.

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int daemon_started_by_inetd(void) {
/* this code also taken from libslack */
/*    static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; */
  static int rc = -1;

  if (rc == -1) {
    /* socklen_t is not available on all platforms (OSX). We define it
     as 'int' at the top of this file if it is missing */
    socklen_t optlen = (socklen_t)sizeof(int);
    int optval;

/*      ptry(pthread_mutex_lock(&lock)) */

    if (rc == -1) {
      rc = (getsockopt(STDIN_FILENO, SOL_SOCKET, SO_TYPE, &optval, &optlen) == 0);
    }

/*      ptry(pthread_mutex_unlock(&lock)) */
  }

  return rc;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  limit_open(): Returns the maximum number of files that a process can
                have open at any time. If indeterminate, a usable
                guess (1024) is returned.

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static long limit_open(void) {
  long value;

  if ((value = sysconf(_SC_OPEN_MAX)) == -1) {
    return 1024L; /* educated guess */
  }

  return value;
}


