/* daemon.c -- Server daemon
 * Created: Fri Feb 28 18:17:56 1997 by faith@dict.org
 * Revised: Tue Apr 23 09:14:45 2002 by faith@dict.org
 * Copyright 1997, 1998, 1999, 2000, 2002 Rickard E. Faith (faith@dict.org)
 * 
 * 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 1, 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, write to the Free Software Foundation, Inc.,
 * 675 Mass Ave, Cambridge, MA 02139, USA.
 * 
 * $Id: daemon.c,v 1.65 2003/07/21 10:02:33 cheusov Exp $
 * 
 */

#include "dictd.h"
#include "index.h"
#include "data.h"
#include "md5.h"
#include "regex.h"
#include "dictdplugin.h"
#include "strategy.h"
#include "plugin.h"

#include <ctype.h>
#include <setjmp.h>

static int          _dict_defines, _dict_matches;
static int          daemonS_in  = 0;
static int          daemonS_out = 0;
static const char   *daemonHostname  = NULL;
static const char   *daemonIP        = NULL;
static int          daemonPort       = -1;
static char         daemonStamp[256] = "";
static jmp_buf      env;
static int          daemonMime;

static void daemon_define( const char *cmdline, int argc, char **argv );
static void daemon_match( const char *cmdline, int argc, char **argv );
static void daemon_show_db( const char *cmdline, int argc, char **argv );
static void daemon_show_strat( const char *cmdline, int argc, char **argv );
static void daemon_show_info( const char *cmdline, int argc, char **argv );
static void daemon_show_server( const char *cmdline, int argc, char **argv );
static void daemon_show( const char *cmdline, int argc, char **argv );
static void daemon_option_mime( const char *cmdline, int argc, char **argv );
static void daemon_option( const char *cmdline, int argc, char **argv );
static void daemon_client( const char *cmdline, int argc, char **argv );
static void daemon_auth( const char *cmdline, int argc, char **argv );
static void daemon_status( const char *cmdline, int argc, char **argv );
static void daemon_help( const char *cmdline, int argc, char **argv );
static void daemon_quit( const char *cmdline, int argc, char **argv );

#define MAXARGCS 3
static struct {
   int        argc;
   const char *name[MAXARGCS];
   void       (*f)( const char *cmdline, int argc, char **argv );
} commandInfo[] = {
   { 1, {"define"},             daemon_define },
   { 1, {"d"},                  daemon_define },
   { 1, {"match"},              daemon_match },
   { 1, {"m"},                  daemon_match },
   { 2, {"show", "db"},         daemon_show_db },
   { 2, {"show", "databases"},  daemon_show_db },
   { 2, {"show", "strat"},      daemon_show_strat },
   { 2, {"show", "strategies"}, daemon_show_strat },
   { 2, {"show", "info"},       daemon_show_info },
   { 2, {"show", "server"},     daemon_show_server },
   { 1, {"show"},               daemon_show },
   { 2, {"option", "mime"},     daemon_option_mime },
   { 1, {"option"},             daemon_option },
   { 1, {"client"},             daemon_client },
   { 1, {"auth"},               daemon_auth },
   { 1, {"status"},             daemon_status },
   { 1, {"s"},                  daemon_status },
   { 1, {"help"},               daemon_help },
   { 1, {"h"},                  daemon_help },
   { 1, {"quit"},               daemon_quit },
   { 1, {"q"},                  daemon_quit },
};
#define COMMANDS (sizeof(commandInfo)/sizeof(commandInfo[0]))

static void *(lookup_command)( int argc, char **argv )
{
   int i, j;
   int err;
   
   for (i = 0; i < COMMANDS; i++) {
      if (argc >= commandInfo[i].argc) {
	 for (err = 0, j = 0; j < commandInfo[i].argc; j++) {
	    if (strcasecmp(argv[j], commandInfo[i].name[j])) {
	       err = 1;
	       break;
	    }
	 }
	 if (!err) return commandInfo[i].f;
      }
   }
   return NULL;
}

static unsigned long daemon_compute_mask(int bits)
{
   unsigned long mask = 0xffffffff;

   if (bits < 1) return 0;
   if (bits < 32) mask -= (1 << (32-bits)) - 1;
   return mask;
}

static void daemon_log( int type, const char *format, ... )
{
   va_list ap;
   char    buf[8*1024];
   char    *buf2;
   int     len;
   char    *s, *d;
   int     c;
   char    marker = '?';

   switch (type) {
   case DICT_LOG_TERM:
      if (!flg_test(LOG_STATS))    return; marker = 'I'; break;
   case DICT_LOG_TRACE:
      if (!flg_test(LOG_SERVER))   return; marker = 'I'; break;
   case DICT_LOG_CLIENT:
      if (!flg_test(LOG_CLIENT))   return; marker = 'C'; break;
   case DICT_LOG_CONNECT:
      if (!flg_test(LOG_CONNECT))  return; marker = 'K'; break;
   case DICT_LOG_DEFINE:
      if (!flg_test(LOG_FOUND))    return; marker = 'D'; break;
   case DICT_LOG_MATCH:
      if (!flg_test(LOG_FOUND))    return; marker = 'M'; break;
   case DICT_LOG_NOMATCH:
      if (!flg_test(LOG_NOTFOUND)) return; marker = 'N'; break;
   case DICT_LOG_COMMAND:
      if (!flg_test(LOG_COMMAND))  return; marker = 'T'; break;
   case DICT_LOG_AUTH:
      if (!flg_test(LOG_AUTH))     return; marker = 'A'; break;
   }

   if (dbg_test(DBG_PORT))
      snprintf(
	 buf, sizeof (buf)/2,
	 ":%c: %s:%d ", marker, daemonHostname, daemonPort );
   else if (flg_test(LOG_HOST))
      snprintf(
	 buf, sizeof (buf)/2,
	 ":%c: %s ", marker, daemonHostname );
   else
      snprintf(
	 buf, sizeof (buf)/2,
	 ":%c: ", marker );

   len = strlen( buf );

   va_start( ap, format );
   vsnprintf( buf+len, sizeof (buf)/2-len, format, ap );
   va_end( ap );
   len = strlen( buf );

   if (len >= sizeof (buf)/2) {
      log_info( ":E: buffer overflow (%d)\n", len );
      buf[2048] = '\0';
      len = strlen(buf);
   }

   buf2 = alloca( 3*(len+3) );

   for (s = buf, d = buf2; *s; s++) {
      c = (unsigned char)*s;
      if (c == '\t')      *d++ = ' ';
      else if (c == '\n') *d++ = c;
      else {
	 if (c < 32)        { *d++ = '^'; *d++ = c + 64;           }
	 else if (c == 127) { *d++ = '^'; *d++ = '?';              }
	 else                 *d++ = c;
      }
   }
   *d = '\0';
   log_info( "%s", buf2 );

   if (d != buf2) d[-1] = '\0';	/* kill newline */
   dict_setproctitle( "dictd %s", buf2 );
}

static int daemon_check_mask(const char *spec, const char *ip)
{
   char           *tmp = alloca(strlen(spec) + 1);
   char           *pt;
   char           tstring[64], mstring[64];
   struct in_addr target, mask;
   int            bits;
   unsigned long  bitmask;
   
   strcpy(tmp, spec);
   if (!(pt = strchr(tmp, '/'))) {
      log_info( ":E: No / in %s, denying access to %s\n", spec, ip);
      return DICT_DENY;
   }
   *pt++ = '\0';
   if (!*pt) {
      log_info( ":E: No bit count after / in %s, denying access to %s\n",
                spec, ip);
      return DICT_DENY;
   }
   
   inet_aton(ip, &target);
   inet_aton(tmp, &mask);
   bits = strtol(pt, NULL, 10);
   strcpy(tstring, inet_ntoa(target));
   strcpy(mstring, inet_ntoa(mask));
   if (bits < 0 || bits > 32) {
      log_info( ":E: Bit count (%d) out of range, denying access to %s\n",
                bits, ip);
      return DICT_DENY;
   }
   
   bitmask = daemon_compute_mask(bits);
   if ((ntohl(target.s_addr) & bitmask) == (ntohl(mask.s_addr) & bitmask)) {
      PRINTF(DBG_AUTH, ("%s matches %s/%d\n", tstring, mstring, bits));
      return DICT_MATCH;
   }
   PRINTF(DBG_AUTH, ("%s does NOT match %s/%d\n", tstring, mstring, bits));
   return DICT_NOMATCH;
   
   return 0;
}

static int daemon_check_range(const char *spec, const char *ip)
{
   char           *tmp = alloca(strlen(spec) + 1);
   char           *pt;
   char           tstring[64], minstring[64], maxstring[64];
   struct in_addr target, min, max;
   
   strcpy(tmp, spec);
   if (!(pt = strchr(tmp, ':'))) {
      log_info( ":E: No : in range %s, denying access to %s\n", spec, ip);
      return DICT_DENY;
   }
   *pt++ = '\0';
   if (strchr(pt, ':')) {
      log_info( ":E: More than one : in range %s, denying access to %s\n",
                spec, ip);
      return DICT_DENY;
   }
   if (!*pt) {
      log_info( ":E: Misformed range %s, denying access to %s\n", spec, ip);
      return DICT_DENY;
   }
   
   inet_aton(ip, &target);
   inet_aton(tmp, &min);
   inet_aton(pt, &max);
   strcpy(tstring, inet_ntoa(target));
   strcpy(minstring, inet_ntoa(min));
   strcpy(maxstring, inet_ntoa(max));
   if (ntohl(target.s_addr) >= ntohl(min.s_addr)
       && ntohl(target.s_addr) <= ntohl(max.s_addr)) {
      PRINTF(DBG_AUTH,("%s in range from %s to %s\n",
                       tstring, minstring, maxstring));
      return DICT_MATCH;
   }
   PRINTF(DBG_AUTH,("%s NOT in range from %s to %s\n",
                    tstring, minstring, maxstring));
   return DICT_NOMATCH;
}

static int daemon_check_wildcard(const char *spec, const char *ip)
{
   char         regbuf[256];
   char         erbuf[100];
   int          err;
   const char   *s;
   char         *d;
   regex_t      re;

   for (d = regbuf, s = spec; s && *s; ++s) {
      switch (*s) {
      case '*': *d++ = '.';  *d++ = '*'; break;
      case '.': *d++ = '\\'; *d++ = '.'; break;
      case '?': *d++ = '.';              break;
      default:  *d++ = *s;               break;
      }
   }
   *d = '\0';
   if ((err = regcomp(&re, regbuf, REG_ICASE|REG_NOSUB))) {
      regerror(err, &re, erbuf, sizeof(erbuf));
      log_info( ":E: regcomp(%s): %s\n", regbuf, erbuf );
      return DICT_DENY;	/* Err on the side of safety */
   }
   if (!regexec(&re, daemonHostname, 0, NULL, 0)
       || !regexec(&re, daemonIP, 0, NULL, 0)) {
      PRINTF(DBG_AUTH,
             ("Match %s with %s/%s\n", spec, daemonHostname, daemonIP));
      regfree(&re);
      return DICT_MATCH;
   }
   regfree(&re);
   PRINTF(DBG_AUTH,
          ("No match (%s with %s/%s)\n", spec, daemonHostname, daemonIP));
   return DICT_NOMATCH;
}

static int daemon_check_list( const char *user, lst_List acl )
{
   lst_Position p;
   dictAccess   *a;
   int          retcode;

   if (!acl)
      return DICT_ALLOW;

   for (p = lst_init_position(acl); p; p = lst_next_position(p)) {
      a = lst_get_position(p);
      switch (a->type) {
      case DICT_DENY:
      case DICT_ALLOW:
      case DICT_AUTHONLY:
         if (strchr(a->spec, '/'))
            retcode = daemon_check_mask(a->spec, daemonIP);
         else if  (strchr(a->spec, ':'))
            retcode = daemon_check_range(a->spec, daemonIP);
         else
            retcode = daemon_check_wildcard(a->spec, daemonIP);
         switch (retcode) {
         case DICT_DENY:
            return DICT_DENY;
         case DICT_MATCH:
            if (a->type == DICT_DENY) {
                daemon_log( DICT_LOG_AUTH, "spec %s denies %s/%s\n",
                            a->spec, daemonHostname, daemonIP);
            }
            return a->type;
         }
	 break;
      case DICT_USER:
	 if (user && !strcmp(user,a->spec)) return DICT_ALLOW;
      case DICT_GROUP:		/* Groups are not yet implemented. */
	 break;
      }
   }
   return DICT_DENY;
}

static int daemon_check_auth( const char *user )
{
   lst_Position p;
   lst_List     dbl = DictConfig->dbl;
   dictDatabase *db;
   
   switch (daemon_check_list( user, DictConfig->acl )) {
   default:
   case DICT_DENY:
      return 1;
   case DICT_AUTHONLY:
      if (!user) return 0;
   case DICT_ALLOW:
      for (p = lst_init_position(dbl); p; p = lst_next_position(p)) {
	 db = lst_get_position(p);
	 switch (daemon_check_list(user, db->acl)) {
	 case DICT_ALLOW: db->available = 1; continue;
	 default:         db->available = 0; continue;
	 }
      }
      break;
   }
   return 0;
}

void daemon_terminate( int sig, const char *name )
{
   alarm(0);
   tim_stop( "t" );
   close(daemonS_in);
   close(daemonS_out);
   if (name) {
      daemon_log( DICT_LOG_TERM,
		  "%s: d/m/c = %d/%d/%d; %sr %su %ss\n",
                  name,
                  _dict_defines,
                  _dict_matches,
                  _dict_comparisons,
                  dict_format_time( tim_get_real( "t" ) ),
                  dict_format_time( tim_get_user( "t" ) ),
                  dict_format_time( tim_get_system( "t" ) ) );
   } else {
      daemon_log( DICT_LOG_TERM,
		  "signal %d: d/m/c = %d/%d/%d; %sr %su %ss\n",
                  sig,
                  _dict_defines,
                  _dict_matches,
                  _dict_comparisons,
                  dict_format_time( tim_get_real( "t" ) ),
                  dict_format_time( tim_get_user( "t" ) ),
                  dict_format_time( tim_get_system( "t" ) ) );
   }

   log_close();
   longjmp(env,1);
   if (sig) exit(sig+128);

   exit(0);
}


static void daemon_write( const char *buf, int len )
{
   int left = len;
   int count;
   
   while (left) {
      if ((count = write(daemonS_out, buf, left)) != left) {
	 if (count <= 0) {
	    if (errno == EPIPE) {
	       daemon_terminate( 0, "pipe" );
	    }
#if HAVE_STRERROR
            log_info( ":E: writing %d of %d bytes:"
                      " retval = %d, errno = %d (%s)\n",
                      left, len, count, errno, strerror(errno) );
#else
            log_info( ":E: writing %d of %d bytes:"
                      " retval = %d, errno = %d\n",
                      left, len, count, errno );
#endif
            daemon_terminate( 0, __FUNCTION__ );
         }
      }
      left -= count;
   }
}

static void daemon_crlf( char *d, const char *s, int dot )
{
   int first = 1;
   
   while (*s) {
      if (*s == '\n') {
	 *d++ = '\r';
	 *d++ = '\n';
	 first = 1;
	 ++s;
      } else {
	 if (dot && first && *s == '.' && s[1] == '\n')
	    *d++ = '.'; /* double first dot on line */
	 first = 0;
	 *d++ = *s++;
      }
   }
   if (dot) {                   /* add final . */
      *d++ = '.';
      *d++ = '\r';
      *d++ = '\n';
   }
   *d = '\0';
}

static void daemon_printf( const char *format, ... )
{
   va_list ap;
   char    buf[BUFFERSIZE];
   char    *pt;
   int     len;

   va_start( ap, format );
   vsnprintf( buf, sizeof (buf), format, ap );
   va_end( ap );
   if ((len = strlen( buf )) >= BUFFERSIZE) {
      log_info( ":E: buffer overflow: %d\n", len );
      daemon_terminate( 0, __FUNCTION__ );
   }

   pt = alloca(2*len);
   daemon_crlf(pt, buf, 0);
   daemon_write(pt, strlen(pt));
}

static void daemon_mime( void )
{
   if (daemonMime) daemon_write( "\r\n", 2 );
}

static void daemon_text( const char *text )
{
   char *pt = alloca( 2*strlen(text) + 10 );

   daemon_crlf(pt, text, 1);
   daemon_write(pt, strlen(pt));
}

static int daemon_read( char *buf, int count )
{
   return net_read( daemonS_in, buf, count );
}

static void daemon_ok( int code, const char *string, const char *timer )
{
   static int lastDefines     = 0;
   static int lastMatches     = 0;
   static int lastComparisons = 0;

   if (code == CODE_STATUS) {
      lastDefines     = 0;
      lastMatches     = 0;
      lastComparisons = 0;
   }

   if (!timer) {
      daemon_printf("%d %s\n", code, string);
   } else {
      tim_stop( timer );
      daemon_printf("%d %s [d/m/c = %d/%d/%d; %sr %su %ss]\n",
                    code,
                    string,
                    _dict_defines - lastDefines,
                    _dict_matches - lastMatches,
                    _dict_comparisons - lastComparisons,
                    dict_format_time( tim_get_real( timer ) ),
                    dict_format_time( tim_get_user( timer ) ),
                    dict_format_time( tim_get_system( timer ) ) );
   }

   lastDefines     = _dict_defines;
   lastMatches     = _dict_matches;
   lastComparisons = _dict_comparisons;
}

static int daemon_count_defs( lst_List list )
{
   lst_Position  p;
   dictWord      *dw;
   unsigned long previousStart = 0;
   unsigned long previousEnd   = 0;
   const char   *previousDef   = NULL;
   int           count         = 0;

   LST_ITERATE(list,p,dw) {
      if (
	 previousStart == dw->start &&
	 previousEnd   == dw->end &&
	 previousDef   == dw->def)
      {
	 continue;
      }

      previousStart = dw->start;
      previousEnd   = dw->end;
      previousDef   = dw->def;

      ++count;
   }
   return count;
}

static void daemon_dump_defs( lst_List list )
{
   lst_Position  p;
   char          *buf;
   dictWord      *dw;
   const dictDatabase  *db;
   unsigned long previousStart = 0;
   unsigned long previousEnd   = 0;
   const char *  previousDef   = NULL;
   int count;

   LST_ITERATE(list,p,dw) {
      db = dw->database;

      if (
	 previousStart == dw->start &&
	 previousEnd   == dw->end &&
	 previousDef   == dw->def) 
      {
	 continue;
      }

      previousStart = dw->start;
      previousEnd   = dw->end;
      previousDef   = dw->def;

      buf = dict_data_obtain ( db, dw );

      if (dw -> database_visible){
	 db = dw -> database_visible;
      }

      daemon_printf (
	  "%d \"%s\" %s \"%s\"\n",
	  CODE_DEFINITION_FOLLOWS,
	  dw->word,
	  db->invisible ? "*" : db->databaseName,
	  db->invisible ? ""  : db->databaseShort);

      daemon_mime();

      if (db->filter){
	 count = strlen(buf);
	 daemon_log( DICT_LOG_AUTH, "filtering with: %s\ncount: %d\n",
		     db->filter, count );

	 dict_data_filter(buf, &count, strlen (buf), db->filter);
	 buf[count] = '\0';
      }

      daemon_text(buf);
      xfree( buf );
   }
}

static int daemon_count_matches( lst_List list )
{
   lst_Position p;
   dictWord     *dw;
   const char   *prevword     = NULL;
   const dictDatabase *prevdb = NULL;
   int           count        = 0;

   LST_ITERATE(list,p,dw) {
      if (prevdb == dw->database && prevword && !strcmp(prevword,dw->word))
	 continue;

      prevword = dw->word;
      prevdb   = dw->database;

      ++count;
   }
   return count;
}

static void daemon_dump_matches( lst_List list )
{
   lst_Position p;
   dictWord     *dw;
   const char   *prevword     = NULL;
   const dictDatabase *prevdb = NULL;
   const dictDatabase *db     = NULL;

   daemon_mime();
   LST_ITERATE(list,p,dw) {
      db = dw -> database;

      if (prevdb == dw->database && prevword && !strcmp(prevword,dw->word))
	  continue;

      prevword = dw->word;
      prevdb   = dw->database;

      if (dw -> database_visible){
	 db = dw -> database_visible;
      }

      daemon_printf (
	  "%s \"%s\"\n",
	  db -> invisible ? "*" : db -> databaseName,
	  dw -> word );
   }
   daemon_printf( ".\n" );
}

static void daemon_banner( void )
{
   time_t         t;

   time(&t);

   snprintf( daemonStamp, sizeof (daemonStamp), "<%d.%d.%lu@%s>", 
	    _dict_forks,
	    getpid(),
	    (long unsigned)t,
	    net_hostname() );
   daemon_printf( "%d %s %s <auth.mime> %s\n",
		  CODE_HELLO,
                  net_hostname(),
		  dict_get_banner(0),
		  daemonStamp );
}

static void daemon_define( const char *cmdline, int argc, char **argv )
{
   lst_List       list = lst_create();
   int            matches = 0;
   char           *word;
   const char     *databaseName;
   int            extension = (argv[0][0] == 'd' && argv[0][1] == '\0');
   int            db_found = 0;

   if (extension) {
      switch (argc) {
      case 2:  databaseName = "*";     word = argv[1]; break;
      case 3:  databaseName = argv[1]; word = argv[2]; break;
      default:
	 daemon_printf( "%d syntax error, illegal parameters\n",
			CODE_ILLEGAL_PARAM );
	 return;
      }
   } else if (argc == 3) {
      databaseName = argv[1];
      word = argv[2];
   } else {
      daemon_printf( "%d syntax error, illegal parameters\n",
		     CODE_ILLEGAL_PARAM );
      return;
   }

   matches = abs(dict_search_databases (
      list, NULL,
      databaseName, word, DICT_EXACT,
      &db_found));

   if (db_found && matches > 0) {
      int actual_matches = daemon_count_defs( list );

      _dict_defines += actual_matches;
      daemon_log( DICT_LOG_DEFINE,
		  "%s \"%s\" %d\n", databaseName, word, actual_matches);
      daemon_printf( "%d %d definitions retrieved\n",
		     CODE_DEFINITIONS_FOUND,
		     actual_matches );
      daemon_dump_defs( list );
      daemon_ok( CODE_OK, "ok", "c" );
#ifdef USE_PLUGIN
      call_dictdb_free (list);
#endif
      dict_destroy_list( list );
      return;
   }

   if (!db_found) {
#ifdef USE_PLUGIN
      call_dictdb_free (list);
#endif
      dict_destroy_list( list );
      daemon_printf( "%d invalid database, use SHOW DB for list\n",
		     CODE_INVALID_DB );
      return;
   }

#ifdef USE_PLUGIN
   call_dictdb_free (list);
#endif
   dict_destroy_list( list );
   daemon_log( DICT_LOG_NOMATCH,
	       "%s exact \"%s\"\n", databaseName, word );
   daemon_ok( CODE_NO_MATCH, "no match", "c" );
}

static void daemon_match( const char *cmdline, int argc, char **argv )
{
   lst_List       list = lst_create();
   int            matches = 0;
   char           *word;
   const char     *databaseName;
   const char     *strategy;
   int            strategyNumber;
   int            extension = (argv[0][0] == 'm' && argv[0][1] == '\0');
   int            db_found = 0;

   if (extension) {
      switch (argc) {
      case 2:databaseName = "*";     strategy = ".";     word = argv[1]; break;
      case 3:databaseName = "*";     strategy = argv[1]; word = argv[2]; break;
      case 4:databaseName = argv[1]; strategy = argv[2]; word = argv[3]; break;
      default:
	 daemon_printf( "%d syntax error, illegal parameters\n",
			CODE_ILLEGAL_PARAM );
	 return;
      }
   } else if (argc == 4) {
      databaseName = argv[1];
      strategy = argv[2];
      word = argv[3];
   } else {
      daemon_printf( "%d syntax error, illegal parameters\n",
		     CODE_ILLEGAL_PARAM );
      return;
   }

   if ((strategyNumber = lookup_strategy(strategy)) < 0) {
      daemon_printf( "%d invalid strategy, use SHOW STRAT for a list\n",
		     CODE_INVALID_STRATEGY );
      return;
   }

   matches = abs(dict_search_databases (
      list, NULL,
      databaseName, word, strategyNumber | DICT_MATCH_MASK,
      &db_found));

   if (db_found && matches > 0) {
      int actual_matches = daemon_count_matches( list );
      
      _dict_matches += actual_matches;
      daemon_log( DICT_LOG_MATCH,
		  "%s %s \"%s\" %d\n",
		  databaseName, strategy, word, actual_matches);
      daemon_printf( "%d %d matches found\n",
		     CODE_MATCHES_FOUND, actual_matches );
      daemon_dump_matches( list );
      daemon_ok( CODE_OK, "ok", "c" );
#ifdef USE_PLUGIN
      call_dictdb_free (list);
#endif
      dict_destroy_list( list );
      return;
   }

   if (!db_found) {
#ifdef USE_PLUGIN
      call_dictdb_free (list);
#endif
      dict_destroy_list( list );
      daemon_printf( "%d invalid database, use SHOW DB for list\n",
		     CODE_INVALID_DB );
      return;
   }

#ifdef USE_PLUGIN
   call_dictdb_free (list);
#endif
   dict_destroy_list( list );
   daemon_log( DICT_LOG_NOMATCH,
	       "%s %s \"%s\"\n", databaseName, strategy, word );
   daemon_ok( CODE_NO_MATCH, "no match", "c" );
}

static lst_Position first_database_pos (void)
{
   return lst_init_position (DictConfig->dbl);
}

static dictDatabase *next_database (
   lst_Position *databasePosition,
   const char *name)
{
   dictDatabase *db = NULL;

   assert (databasePosition);

   if (!name)
      return NULL;

   if (*name == '*' || *name == '!') {
      if (*databasePosition) {
	 do {
	    db = lst_get_position( *databasePosition );
	    *databasePosition = lst_next_position( *databasePosition );
	 } while (db && !db->available);
      }
      return db;
   } else {
      while (*databasePosition) {
         db = lst_get_position( *databasePosition );
	 *databasePosition = lst_next_position( *databasePosition );
         if (db && !strcmp(db -> databaseName,name) && !db -> invisible) {
	    if (db->available)
	       return db;
	    else
               return NULL;
	 }
      }
      return NULL;
   }
}

static int count_databases( void )
{
   int count = 0;
   const dictDatabase *db;

   lst_Position databasePosition = first_database_pos ();

   while (NULL != (db = next_database (&databasePosition, "*"))){
      if (!db->invisible){
	 ++count;
      }
   }

   return count;
}

static void destroy_word_list (lst_List l)
{
   char *word;

   while (lst_length (l)){
      word = lst_pop (l);
      if (word)
	 xfree (word);
   }
   lst_destroy (l);
}

/*
  Replaces invisible databases with db argument.
 */
static void replace_invisible_databases (
   lst_Position *pos,
   const dictDatabase *db)
{
   dictWord *dw;

   while (pos){
      dw = (dictWord *) lst_get_position (pos);

      if (dw -> database && dw -> database -> invisible){
	 dw -> database_visible = db;
      }

      pos = lst_next_position (pos);
   }
}

/*
  Search for all words in word_list in the database db
 */
static int dict_search_words (
   lst_List *l,
   lst_List word_list,
   const dictDatabase *db,
   int strategy,
   int *error, int *result,
   const dictPluginData **extra_result, int *extra_result_size)
{
   lst_Position word_list_pos;
   int mc = 0;
   int matches_count = 0;
   const char *word;

   int old_count = 0;
   int db_found  = 0;

   word_list_pos = lst_init_position (word_list);
   while (word_list_pos){
      word = lst_get_position (word_list_pos);

      if (word){
	 if (db -> virtual_db_list){
	    assert (lst_init_position (db -> virtual_db_list));

	    old_count = lst_length (l);

	    matches_count = dict_search_databases (
	       l, lst_init_position (db -> virtual_db_list),
	       "*", word, strategy, &db_found);

	    assert (db_found);

	    if (matches_count > 0){
	       replace_invisible_databases (
		  lst_nth_position (l, old_count + 1),
		  db);
	    }
	 }else{
	    matches_count = dict_search (
	       l, word, db, strategy,
	       result, extra_result, extra_result_size);

	    if (*result == DICT_PLUGIN_RESULT_PREPROCESS){
	       assert (matches_count > 0);

	       xfree (lst_get_position (word_list_pos));
	       lst_set_position (word_list_pos, NULL);
	    }
	 }

	 if (matches_count < 0){
	    *error = 1;
	    matches_count = abs (matches_count);
	    mc += matches_count;
	    break;
	 }

	 mc += matches_count;
      }

      word_list_pos = lst_next_position (word_list_pos);
   }

   return mc;
}

int dict_search_databases (
   lst_List *l,
   lst_Position databasePosition, /* NULL for global database list */
   const char *databaseName, const char *word, int strategy,
   int *db_found)
{
   int matches       = -1;
   int matches_count = 0;
   int error         = 0;


   const dictDatabase *db;
   dictWord *dw;
   char *p;

   lst_List preprocessed_words;
   int i;

   int                   result;
   const dictPluginData *extra_result;
   int                   extra_result_size;

   *db_found = 0;

   if (!databasePosition)
      databasePosition = first_database_pos ();

   preprocessed_words = lst_create ();
   lst_append (preprocessed_words, xstrdup(word));

   while (!error && (db = next_database (&databasePosition, databaseName))) {
      if (db -> exit_db)
	 /* dictionary_exit */
	 break;

      *db_found = 1;

      result = DICT_PLUGIN_RESULT_NOTFOUND;

      matches_count = dict_search_words (
	 l,
	 preprocessed_words, db, strategy,
	 &error, &result, &extra_result, &extra_result_size);

      if (matches < 0)
	 matches = 0;

      if (result == DICT_PLUGIN_RESULT_PREPROCESS){
	 for (i=0; i < matches_count; ++i){
	    dw = lst_pop (l);
	    switch (dw -> def_size){
	    case -1:
	       p = xstrdup (dw -> word);
	       lst_append (preprocessed_words, p);
	       break;
	    case 0:
	       break;
	    default:
	       p = xmalloc (1 + dw -> def_size);
	       memcpy (p, dw -> def, dw -> def_size);
	       p [dw -> def_size] = 0;

	       lst_append (preprocessed_words, p);
	    }

	    dict_destroy_datum (dw);
	 }
      }else{
	 matches += matches_count;

	 if (result == DICT_PLUGIN_RESULT_EXIT)
	    break;

	 if (*databaseName == '*')
	    continue;
	 else if (!matches && *databaseName == '!')
	    continue;

	 break;
      }
   }

   destroy_word_list (preprocessed_words);

   return error ? -matches : matches;
}

static void daemon_show_db( const char *cmdline, int argc, char **argv )
{
   int          count;
   const dictDatabase *db;
   
   lst_Position databasePosition;

   if (argc != 2) {
      daemon_printf( "%d syntax error, illegal parameters\n",
		     CODE_ILLEGAL_PARAM );
      return;
   }

   if (!(count = count_databases())) {
      daemon_printf( "%d no databases present\n", CODE_NO_DATABASES );
   } else {
      daemon_printf( "%d %d databases present\n",
		     CODE_DATABASE_LIST, count );

      databasePosition = first_database_pos ();

      daemon_mime();
      while ((db = next_database(&databasePosition, "*"))) {
	 if (!db->invisible){
	    daemon_printf( "%s \"%s\"\n",
			   db->databaseName, db->databaseShort );
	 }
      }
      daemon_printf( ".\n" );
      daemon_ok( CODE_OK, "ok", NULL );
   }
}

static void daemon_show_strat( const char *cmdline, int argc, char **argv )
{
   int i;

   int strat_count        = get_strategy_count ();
   dictStrategy ** strats = get_strategies ();

   if (argc != 2) {
      daemon_printf( "%d syntax error, illegal parameters\n",
		     CODE_ILLEGAL_PARAM );
      return;
   }

   if (strat_count){
      daemon_printf( "%d %d databases present\n",
		     CODE_STRATEGY_LIST, strat_count );
      daemon_mime();

      for (i = 0; i < strat_count; i++) {
	 daemon_printf( "%s \"%s\"\n",
			strats [i] -> name, strats [i] -> description );
      }

      daemon_printf( ".\n" );
      daemon_ok( CODE_OK, "ok", NULL );
   }else{
      daemon_printf( "%d no strategies available\n", CODE_NO_STRATEGIES );
   }
}

static void daemon_show_info( const char *cmdline, int argc, char **argv )
{
   char         *buf;
   dictWord     *dw;
   const dictDatabase *db;
   lst_List     list;
   const char  *info_entry_name = DICT_INFO_ENTRY_NAME;

   lst_Position databasePosition = first_database_pos ();

   if (argc != 3) {
      daemon_printf( "%d syntax error, illegal parameters\n",
		     CODE_ILLEGAL_PARAM );
      return;
   }

   if ((argv[2][0] == '*' || argv[2][0] == '!') && argv[2][1] == '\0') {
      daemon_printf( "%d invalid database, use SHOW DB for list\n",
		     CODE_INVALID_DB );
      return;
   }

   list = lst_create();
   while ((db = next_database(&databasePosition, argv[2] ))) {
      if (
	 db -> databaseInfo &&
	 (db -> virtual_db || db -> databaseInfo [0] != '@'))
      {
	 daemon_printf( "%d information for %s\n",
			CODE_DATABASE_INFO, argv[2] );
	 daemon_mime();
	 daemon_text(db -> databaseInfo);
	 daemon_text("\n");
	 daemon_ok( CODE_OK, "ok", NULL );
	 return;
      }

      if (db -> databaseInfo && db -> databaseInfo [0] == '@')
	 info_entry_name = db -> databaseInfo + 1;

      if (dict_search (
	 list,
	 info_entry_name,
	 db,
	 DICT_EXACT,
	 NULL, NULL, NULL))
      {
	 dw = lst_nth_get( list, 1 );
	 buf = dict_data_obtain( db, dw );

	 dict_destroy_list( list );
	 daemon_printf( "%d information for %s\n",
			CODE_DATABASE_INFO, argv[2] );
	 daemon_mime();
	 daemon_text(buf);
	 daemon_ok( CODE_OK, "ok", NULL );
	 return;
      } else {
	 dict_destroy_list( list );
	 daemon_printf( "%d information for %s\n",
			CODE_DATABASE_INFO, argv[2] );
	 daemon_mime();
	 daemon_text( "No information available\n" );
	 daemon_ok( CODE_OK, "ok", NULL );
	 return;
      }
   }

   dict_destroy_list( list );
   daemon_printf( "%d invalid database, use SHOW DB for list\n",
		  CODE_INVALID_DB );
}

static void daemon_show_server( const char *cmdline, int argc, char **argv )
{
   FILE          *str;
   char          buffer[1024];
   const dictDatabase  *db;
   double        uptime;
   
   lst_Position databasePosition = first_database_pos ();

   tim_stop("dictd");
   uptime = tim_get_real("dictd");
   
   daemon_printf( "%d server information\n", CODE_SERVER_INFO );
   daemon_mime();
   daemon_printf( "%s\n", dict_get_banner(0) );

   if (!inetd){
      daemon_printf (
	 "On %s: up %s, %d fork%s (%0.1f/hour)\n",
	 net_hostname(),
	 dict_format_time( uptime ),
	 _dict_forks,
	 _dict_forks > 1 ? "s" : "",
	 (_dict_forks/uptime)*3600.0 );
   }

   if (count_databases()) {
      daemon_printf( "\nDatabase      Headwords         Index"
		     "          Data  Uncompressed\n" );
      while ((db = next_database (&databasePosition, "*"))) {
	 int headwords       = db->index ? db->index->headwords : 0;

	 int index_size      = 0;
	 char index_size_uom = 'k';

	 int data_size       = 0;
	 char data_size_uom  = 'k';
	 int data_length     = 0;
	 char data_length_uom= 'k';

	 if (db -> invisible)
	    continue;

	 if (db->index){
	    index_size = db->index->size/1024 > 10240 ?
	       db->index->size/1024/1024 : db->index->size/1024;
	    index_size_uom = db->index->size/1024 > 10240 ? 'M' : 'k';
	 }

	 if (db->data){
	    data_size = db->data->size/1024 > 10240 ?
	       db->data->size/1024/1024 : db->data->size/1024;
	    data_size_uom = db->data->size/1024 > 10240 ? 'M' : 'k';

	    data_length = db->data->length/1024 > 10240 ?
	       db->data->length/1024/1024 : db->data->length/1024;
	    data_length_uom = db->data->length/1024 > 10240 ? 'M' : 'k';
	 }

	 daemon_printf(
	    "%-12.12s %10lu %10lu %cB %10lu %cB %10lu %cB\n",
	    db->databaseName,
	    headwords,

	    index_size,
	    index_size_uom,

	    data_size,
	    data_size_uom,

	    data_size,
	    data_size_uom);
      }
   }

   if (DictConfig->site && (str = fopen( DictConfig->site, "r" ))) {
      daemon_printf( "\nSite-specific information for %s:\n\n",
		     net_hostname() );
      while ((fgets( buffer, 1000, str ))) daemon_printf( "%s", buffer );
      fclose( str );
   }
   daemon_printf( ".\n" );
   daemon_ok( CODE_OK, "ok", NULL );
}

static void daemon_show( const char *cmdline, int argc, char **argv )
{
   daemon_printf( "%d syntax error, illegal parameters\n",
		  CODE_ILLEGAL_PARAM );
}

static void daemon_option_mime( const char *cmdline, int argc, char **argv )
{
   ++daemonMime;
   daemon_ok( CODE_OK, "ok - using MIME headers", NULL );
}

static void daemon_option( const char *cmdline, int argc, char **argv )
{
   daemon_printf( "%d syntax error, illegal parameters\n",
		  CODE_ILLEGAL_PARAM );
}

static void daemon_client( const char *cmdline, int argc, char **argv )
{
   const char *pt = strchr( cmdline, ' ' );
   
   if (pt)
      daemon_log( DICT_LOG_CLIENT, "%.200s\n", pt + 1 );
   else
      daemon_log( DICT_LOG_CLIENT, "%.200s\n", cmdline );
   daemon_ok( CODE_OK, "ok", NULL );
}

static void daemon_auth( const char *cmdline, int argc, char **argv )
{
   char              *buf;
   hsh_HashTable     h = DictConfig->usl;
   const char        *secret;
   struct MD5Context ctx;
   unsigned char     digest[16];
   char              hex[33];
   int               i;
   int               buf_size;

   if (argc != 3)
      daemon_printf( "%d syntax error, illegal parameters\n",
		     CODE_ILLEGAL_PARAM );
   if (!h || !(secret = hsh_retrieve(h, argv[1]))) {
      daemon_log( DICT_LOG_AUTH, "%s@%s/%s denied: no invalid username\n",
                  argv[1], daemonHostname, daemonIP );
      daemon_printf( "%d auth denied\n", CODE_AUTH_DENIED );
      return;
   }

   buf_size = strlen(daemonStamp) + strlen(secret) + 10;
   buf = alloca(buf_size);
   snprintf( buf, buf_size, "%s%s", daemonStamp, secret );

   MD5Init(&ctx);
   MD5Update(&ctx, buf, strlen(buf));
   MD5Final(digest, &ctx);

   for (i = 0; i < 16; i++)
      snprintf( hex+2*i, 2, "%02x", digest[i] );

   hex[32] = '\0';

   PRINTF(DBG_AUTH,("Got %s expected %s\n", argv[2], hex ));

   if (strcmp(hex,argv[2])) {
      daemon_log( DICT_LOG_AUTH, "%s@%s/%s denied: hash mismatch\n",
                  argv[1], daemonHostname, daemonIP );
      daemon_printf( "%d auth denied\n", CODE_AUTH_DENIED );
   } else {
      daemon_printf( "%d authenticated\n", CODE_AUTH_OK );
      daemon_check_auth( argv[1] );
   }
}

static void daemon_status( const char *cmdline, int argc, char **argv )
{
   daemon_ok( CODE_STATUS, "status", "t" );
}

static void daemon_help( const char *cmdline, int argc, char **argv )
{
   daemon_printf( "%d help text follows\n", CODE_HELP );
   daemon_mime();
   daemon_text(
    "DEFINE database word         -- look up word in database\n"
    "MATCH database strategy word -- match word in database using strategy\n"
    "SHOW DB                      -- list all accessible databases\n"
    "SHOW DATABASES               -- list all accessible databases\n"
    "SHOW STRAT                   -- list available matching strategies\n"
    "SHOW STRATEGIES              -- list available matching strategies\n"
    "SHOW INFO database           -- provide information about the database\n"
    "SHOW SERVER                  -- provide site-specific information\n"
    "OPTION MIME                  -- use MIME headers\n"
    "CLIENT info                  -- identify client to server\n"
    "AUTH user string             -- provide authentication information\n"
    "STATUS                       -- display timing information\n"
    "HELP                         -- display this help information\n"
    "QUIT                         -- terminate connection\n\n"
    "The following commands are unofficial server extensions for debugging\n"
    "only.  You may find them useful if you are using telnet as a client.\n"
    "If you are writing a client, you MUST NOT use these commands, since\n"
    "they won't be supported on any other server!\n\n"
    "D word                       -- DEFINE * word\n"
    "D database word              -- DEFINE database word\n"
    "M word                       -- MATCH * . word\n"
    "M strategy word              -- MATCH * strategy word\n"
    "M database strategy word     -- MATCH database strategy word\n"
    "S                            -- STATUS\n"
    "H                            -- HELP\n"
    "Q                            -- QUIT\n"
   );
   daemon_ok( CODE_OK, "ok", NULL );
}

static void daemon_quit( const char *cmdline, int argc, char **argv )
{
   daemon_ok( CODE_GOODBYE, "bye", "t" );
   daemon_terminate( 0, "quit" );
}

/* The whole sub should be moved here, but I want to keep the diff small. */
int _handleconn (int delay, int error);

int dict_inetd(char ***argv0, int delay, int error )
{
   if (setjmp(env)) return 0;

   daemonPort = -1;
   daemonIP   = "inetd";

   daemonHostname = daemonIP;

   daemonS_in        = 0;
   daemonS_out       = 0;

   return _handleconn(delay, error);
}

int dict_daemon( int s, struct sockaddr_in *csin, char ***argv0, int delay,
		 int error )
{
   struct hostent *h;
	
   if (setjmp(env)) return 0;

   daemonPort = ntohs(csin->sin_port);
   daemonIP   = str_find( inet_ntoa(csin->sin_addr) );
   if ((h = gethostbyaddr((void *)&csin->sin_addr,
			  sizeof(csin->sin_addr), csin->sin_family))) {
      daemonHostname = str_find( h->h_name );
   } else
      daemonHostname = daemonIP;

   daemonS_in        = s;
   daemonS_out       = s;

   return _handleconn(delay, error);
}

int _handleconn (int delay, int error) {
   char           buf[4096];
   int            count;
   arg_List       cmdline;
   int            argc;
   char           **argv;
   void           (*command)(const char *, int, char **);
      
   _dict_defines     = 0;
   _dict_matches     = 0;
   _dict_comparisons = 0;

   tim_start( "t" );
   daemon_log( DICT_LOG_TRACE, "connected\n" );
   daemon_log( DICT_LOG_CONNECT, "%s/%s connected on port %d\n",
               daemonHostname, daemonIP, daemonPort );
   dict_setproctitle( "dictd: %s connected", daemonHostname );

   if (error) {
      daemon_printf( "%d temporarily unavailable\n",
		     CODE_TEMPORARILY_UNAVAILABLE );
      daemon_terminate( 0, "temporarily unavailable" );
   }

   if (daemon_check_auth( NULL )) {
      daemon_log( DICT_LOG_AUTH, "%s/%s denied: ip/hostname rules\n",
                  daemonHostname, daemonIP );
      daemon_printf( "%d access denied\n", CODE_ACCESS_DENIED );
      daemon_terminate( 0, "access denied" );
   }
	      
   daemon_banner();

   alarm(delay);
   while ((count = daemon_read( buf, 4000 )) >= 0) {
      alarm(0);
      tim_start( "c" );
      if (!count) {
#if 0
         daemon_ok( CODE_OK, "ok", "c" );
#endif
	 continue;
      }

      daemon_log( DICT_LOG_COMMAND, "%.80s\n", buf );
      cmdline = arg_argify(buf,0);
      arg_get_vector( cmdline, &argc, &argv );
      if ((command = lookup_command(argc,argv))) {
	 command(buf, argc, argv);
      } else {
	 daemon_printf( "%d unknown command\n", CODE_SYNTAX_ERROR );
      }
      arg_destroy(cmdline);
      alarm(delay);
   }
#if 0
   printf( "%d %d\n", count, errno );
#endif
   daemon_terminate( 0, "close" );
   return 0;
}
