/* Nessus
 * Copyright (C) 1998, 1999, 2000, 2001 Renaud Deraison
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2,
 * as published by the Free Software Foundation
 *
 * 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.
 *
 * In addition, as a special exception, Renaud Deraison
 * gives permission to link the code of this program with any
 * version of the OpenSSL library which is distributed under a
 * license identical to that listed in the included COPYING.OpenSSL
 * file, and distribute linked combinations including the two.
 * You must obey the GNU General Public License in all respects
 * for all of the code used other than OpenSSL.  If you modify
 * this file, you may extend this exception to your version of the
 * file, but you are not obligated to do so.  If you do not wish to
 * do so, delete this exception statement from your version.
 *
 * Modified by Axel Nennker axel@nennker.de 20020306
 * Removed unused variables and format string errors.
 */

/** @file
 * Functions to explore a report. Module name is misleading (has nothing to do
 * with data mining).
 * 
 * Note that this code can easily be
 * changed to be linked to any database instead of the simple
 * flat files that are currently used.
 * This file implements subset selection in the report as well
 * as the implementation of a light query language.
 * 
 * @section queryreport Querying a report
 *
 * We use a dumb SQL-like language to query our subsets. If a database
 * was to be linked, it should intercept these calls, rephrase them
 * and return the results as subsets.
 * 
 * The syntax is :
 *
 * SELECT \<category [,category...]\> FROM \<table\> [WHERE \<category\> = \<value\>
 [AND \<category\> = \<value\> ...]]
 *
 *
 * where \<category\> is one of :
 *	- subnet
 *	- host
 *	- port
 *	- note	(security note)
 *	- warning (security warning)
 *	- hole  (security hole)
 *	- plugin id
 *
 * \<select\> may return multiple times the same entries. Hence it's wise
 * to use subset_uniq() on the output of a query.
 *
 *
 * Valid statements :
 *
 *	SELECT host FROM results WHERE subnet = '127.0.0'\n
 *	SELECT host,severity FROM results WHERE subnet = '127.0.0'\n
 *	SELECT host,severity,port FROM results WHERE subnet = '127.0.0'\n
 * 
 * 
 * @section subsetmanagement Subset Management Interface
 *
 * @ref subset
 * A subset contains the result of a query. It is made up of rows
 * and fields (that we call values).
 * To go from the current row to the next one, use the function
 * subset_next(). To extract value of the values (fields),
 * use subset_nth_value(). subset_value() is an alias for
 * subset_nth_value(subset, 0), ie: it returns the first field
 * (the only one which can not be NULL).
 */

#include <includes.h>
#include <stdarg.h>
#include "backend.h"
#include "context.h"
#include "data_mining.h"
#include "severity_filter.h"
#include "subset.h"

#ifndef MIN
#define MIN(x,y) ((x<y)?(x):(y))
#endif


#ifdef HAVE_MMAP
#ifndef MAP_FAILED
#define MAP_FAILED (void*)-1
#endif
#endif


/**
 * @brief Extern backend struct array.
 */
extern struct backend backends[];
extern struct context* Context;

static int __split_line(char*, char**, char**, char**, char**, char**, char**, char**);

/*----------------------------------------------------------------*
 * Private utilities                                              *
 *----------------------------------------------------------------*/
 
#ifdef HAVE_MMAP

static unsigned int
mkfhash(char * name)
{
 unsigned int ret = 0;
 while ( name[0] != '\0' ) {
	ret += name[0];
	name++;
	}

 return ret % BE_HASH_MAX;
}


struct field *
field_get (struct hfield * h, char * name)
{
 unsigned int idx = mkfhash(name);
 struct field * ret;

 ret = h->hfield[idx];
 while ( ret != NULL )
 {
  if ( strcmp(name, ret->value) == 0 ) return ret;
  ret = ret->next;
 }

 return NULL;
}


void
field_add (struct hfield * h, struct field * f)
{
 unsigned int idx = mkfhash(f->value);

 f->next = h->hfield[idx];
 h->hfield[idx] = f;
}


int
be_index_keywords (be)
{
  int i;
  char * field[BE_NUM_FIELDS];
  static char * buf = NULL;
  static int bufsz = 0;


  if ( buf == NULL )
  {
   bufsz = 1024 * 1024;
   buf   = emalloc ( bufsz );
  }

  if(backends[be].fields)
    return 0;

  backends[be].fields = emalloc(BE_NUM_FIELDS * sizeof(struct hfield*));
  for ( i = 0 ; i < BE_NUM_FIELDS ; i ++ )
  {
   backends[be].fields[i] = emalloc(sizeof(struct hfield));
  }

  for(i=0;i<backends[be].num_lines;i++)
  {
  int len;
  int j;
  char * sol = backends[be].lines[i];
  char * eol = backends[be].eols[i];
  int maxidx;
  
  
  if(eol)
  {
   len = (int)(eol - sol);
   memcpy(buf, sol, MIN(len, bufsz - 1));
   buf[MIN(len,bufsz - 1)] = '\0';
  }
  else
  {
   len = strlen(backends[be].lines[i]);
   /* RATS: ignore, Given limit is sane. */
   memcpy(buf, backends[be].lines[i], MIN(bufsz - 1, len));
   buf[MIN(len, bufsz - 1)] = '\0';
  }
  
  __split_line(buf, &field[0], &field[1], &field[2], &field[3], &field[4], &field[5], &field[6]);
  
  /*
   * We don't index the last field (the data), hence the BE_NUM_FIELDS-1
   */
  maxidx = BE_NUM_FIELDS - 1;
  if ( strcmp(field[0], "timestamps") == 0 ) maxidx = 4;

  for(j=0;j<maxidx;j++)
  {
   struct field * f;
   int flag = 0;
 
   if(!field[j])
     continue;
	
   f = field_get(backends[be].fields[j], field[j]);
   if ( f != NULL )
   {
      f->lines[f->num_lines++] = i;
      if(f->num_lines >= f->allocated_lines)
      {
       f->allocated_lines *= 2;
       f->lines = realloc(f->lines, f->allocated_lines*sizeof(int));
      }
      flag = 1;
   }
    
    if(!flag && field[j][0] != '\0' )
    {
     if ( field[j] == NULL )
       	return -1;
    f = emalloc(sizeof(*f));
    f->value = emalloc(strlen(field[j])+1);
    strcpy(f->value, field[j]); /* RATS: ignore, Correct buffer length ensured by line above. */
    f->lines = emalloc(sizeof(int)*5);
    f->allocated_lines = 5;
    f->num_lines = 1;
    f->lines[0] = i;  
    field_add(backends[be].fields[j], f);
   }
  }

}
  
 for(i=0;i<6;i++)
 {
  struct hfield * h = backends[be].fields[i];
  int i;
  for ( i = 0 ; i < BE_HASH_MAX ; i ++ )
  {
   struct field * f = h->hfield[i];
   while(f)
   {
    f->allocated_lines = f->num_lines;
    f->lines = realloc(f->lines, f->allocated_lines*sizeof(int));
    f = f->next;
   }
  }
 }
 return 0;
}

static void
be_mk_index (int be)
 {
   int num_lines = 0;
   int num_allocated_lines = 65535;
   char ** lines;
   char ** eols;
   char * sol, * eol;
   char * eof = NULL;
   
   lines = emalloc(num_allocated_lines*sizeof(*lines));
   eols  = emalloc(num_allocated_lines*sizeof(*eols));
   sol = backends[be].mmap;
   eof = sol + backends[be].mmap_size;
  
   num_lines = 0;
   while(sol != NULL && sol != eof )
   {
    eol = strchr(sol, '\n');
    lines[num_lines] = sol;
    eols[num_lines] = eol;
    num_lines++;
    if(num_lines + 1 >= num_allocated_lines)
    {
     num_allocated_lines *= 2;
     lines = realloc(lines, num_allocated_lines*sizeof(*lines));
     eols = realloc(eols, num_allocated_lines*sizeof(*eols));
    }
    if(eol)sol = &(eol[1]);
    else sol = NULL;
   }
   backends[be].lines = realloc(lines, (num_lines + 1)*sizeof(*lines));
   backends[be].eols = realloc(eols, (num_lines + 1)*sizeof(*eols));
   backends[be].num_lines = num_lines;
   backends[be].lines[num_lines] = NULL;
   backends[be].eols[num_lines] = NULL;
   backends[be].cur_line = 0;
#ifdef DEBUG
   printf("data_mining.c: Finished the indexing - %d lines\n", num_lines);
#endif
   be_index_keywords(be);
}

#endif

// GLib >= 2.8: GMappedFile
#ifdef HAVE_MMAP
static int
mmap_read_line_n (int be, char * buf, size_t size, int n)
{
 char* sol;
 char* eol;

 if (size <= 0)
  return -1;

 size --;
 sol = backends[be].lines[n];
 if(!sol)
  return -1;
 eol = backends[be].eols[n];
 if(eol)
 {
   int line_len = (int)(eol - sol);
   memcpy(buf, sol, MIN(line_len, size));
   buf[MIN(line_len, size)] = '\0';
   backends[be].cur_line++;
   return MIN(line_len, size);
 }
 else
 {
   int len = strlen(backends[be].lines[n]);
   memcpy(buf, backends[be].lines[n], MIN(size, len));
   buf[MIN(size, len)] = '\0';
   backends[be].cur_line++;
   return MIN(size, len);		
 }
}
#endif

/**
 * @brief Returns one record (one line) read from the backend (flatfile).
 *
 * We use mmap() if possible, for speed sake. For extremely big files,
 * the use of a real database would be required.
 * 
 * @return One record (one line) read from the backend (flatfile).
 */
static int
read_line (int be, char* buf, size_t size)
{
// GLib >= 2.8 : GMappedFile
#ifdef HAVE_MMAP
 /* We always try to mmap() our file. If we don't have enough memory,
  * we expect the OS to not be dumb and return MAP_FAILED instead of
  * trying to fit 2Gb of data into memory
  *
  * We could probably improve that in the future by mapping only segments
  * of the file at once, thus allowing us to work on the file quite fast
  * (compared to just using read()). */
 if(!backends[be].mmap_attempts)
 {
  struct stat buf;
  int len;
  fstat(backends[be].fd, &buf);
  len = (int)buf.st_size;
  backends[be].mmap_size = len;
  if((backends[be].mmap = 
   	mmap(NULL, len, PROT_READ, MAP_SHARED, backends[be].fd, 0)) == MAP_FAILED)
   	backends[be].mmap = NULL;
  else
  	 be_mk_index(be);
  backends[be].mmap_attempts++;
 }
 
 if(backends[be].mmap)
 {
  char * eol, *sol;
  int line_len;
  
  
  sol = backends[be].lines[backends[be].cur_line];
  if(!sol)
   {
  	return -1; /* eof */
   }
	
  eol = backends[be].eols[backends[be].cur_line];
  if ( size <= 0 )
	return -1;
  size --;
  
  if(eol)
  {
   line_len = (int)(eol - sol);
   memcpy(buf, sol, MIN(line_len, size));
   buf[MIN(line_len, size)] = '\0';
   backends[be].cur_line++;
   return line_len;
  }
  else
  {
   int len = strlen(backends[be].lines[backends[be].cur_line]);
   memcpy(buf, backends[be].lines[backends[be].cur_line], MIN(size, len));
   buf[len] = '\0';
   backends[be].cur_line++;
   return len;		
  }
 }
 else
#endif /* HAVE_MMAP */

 /* This part could be improved too. Rather than reading one byte
  * at a time, we could read, say, 255 bytes and use fseek() to
  * reset the position where needed. */
 {
 int tot = 1;
 bzero (buf, size);


 if (read(backends[be].fd, buf, 1) <= 0)
  return -1;
 while(buf[0] != '\n')
 {
  buf++;
  tot++;
  if(tot > size)
   return tot;
  if(read(backends[be].fd, buf, 1) < 0)
   return -1;
  }
  return tot;
 }
}

/*---------------------------------------------------------------------*
 * Data mining functions                                               *
 *---------------------------------------------------------------------*/

#define QUERY_TYPE_SUBNET 	1
#define QUERY_TYPE_HOST	 	2
#define QUERY_TYPE_PORT	        3
#define QUERY_TYPE_PLUGIN_ID    4
#define QUERY_TYPE_SEVERITY	5
#define QUERY_TYPE_REPORT       6

#define QUERY_TYPE_TYPE	3
#define QUERY_TYPE_DATE	4


#define QUERY_TYPE_SUBNET_ASC 		"subnet"
#define QUERY_TYPE_HOST_ASC   		"host"
#define QUERY_TYPE_PORT_ASC   		"port"
#define QUERY_TYPE_PLUGIN_ID_ASC 	"plugin_oid"
#define QUERY_TYPE_SEVERITY_ASC		"severity"
#define QUERY_TYPE_REPORT_ASC   	"report"
#define QUERY_TYPE_TYPE_ASC		"type"
#define QUERY_TYPE_DATE_ASC		"date"


#define QUERY_OP_AND		1
#define QUERY_OP_OR		2

#define QUERY_OP_AND_ASC 	"AND"



/** Maximum number of fields the user can ask for */
#define MAX_QUERIES 		20


struct condition {
  struct condition * next;
  int type;
  int operator;
  char value[1];
};


struct query {
  char * table;
  int type[MAX_QUERIES];
  int num;
  int uniq;
  struct condition * conditions;
};


#ifdef HAVE_MMAP
static u_short *
requests2lines (int be, char * requests[BE_NUM_FIELDS])
{
 u_short * ret = emalloc(backends[be].num_lines*sizeof(u_short));
 int i;
 int max = 0;
 for(i=0;i<BE_NUM_FIELDS;i++)
 {
  if(requests[i])
  {
   struct field * f = field_get(backends[be].fields[i], requests[i]);
   int j;
   max++;
   if(f != NULL )
   {
      for (j=0; j<f->num_lines; j++)
        ret[f->lines[j]]++;
   }
  }
 }

 for (i=0; i<backends[be].num_lines; i++)
  {
    if (ret[i] < max) ret[i] = 0;
  }
return ret;
}
#endif /* HAVE_MMAP */


static int
str2querytype (char * str)
{
 int type;

 if(!strcmp(str, QUERY_TYPE_SUBNET_ASC))
  type = QUERY_TYPE_SUBNET;
 else if(!strcmp(str, QUERY_TYPE_HOST_ASC))
   type = QUERY_TYPE_HOST;
 else if(!strcmp(str, QUERY_TYPE_PORT_ASC))
   type = QUERY_TYPE_PORT;
 else if(!strcmp(str, QUERY_TYPE_PLUGIN_ID_ASC))
   type = QUERY_TYPE_PLUGIN_ID;
 else if(!strcmp(str, QUERY_TYPE_SEVERITY_ASC))
   type = QUERY_TYPE_SEVERITY;
 else if(!strcmp(str, QUERY_TYPE_REPORT_ASC))
   type = QUERY_TYPE_REPORT;
 else if(!strcmp(str, QUERY_TYPE_TYPE_ASC))
   type = QUERY_TYPE_TYPE;
 else if(!strcmp(str, QUERY_TYPE_DATE_ASC))
   type = QUERY_TYPE_DATE;
 else
   type = -1;

  return type;
}

static int
str2querytypes (char * str, struct query * query)
{
 char * t;
 query->num = 0;

 while(str)
 {
  int type;
  t = strchr(str, ',');
  if(t)t[0] = '\0';
  type = str2querytype(str);
  if(type < 0)
   return type;
  query->type[query->num++] = type;
  if(!t)str = NULL;
  else str = &(t[1]);
 }
 return 0;
}

static int
str2querycondition (char * str)
{
 if(!strcmp(str, QUERY_OP_AND_ASC))
   return QUERY_OP_AND;
 else return -1;
}


/**
 * @brief Parses the condition(s) of a query (the whole part after 'where').
 */
static struct condition *
compile_conditions (char * str)
{
 struct condition * ret = NULL;
 int operator = QUERY_OP_AND;
 for(;;)
 {
  int type = 0;
  char * t;
  struct condition * condition;

  if(!str || (str[0] == '\0'))
   break;

  while(str[0]==' ')
    str++;

  t = &(str[1]);
  while(t[0]!='=' &&
        t[0])
	{
	 if(t[0]==' ')t[0]='\0';
	 t++;
	}

  if(!t[0])
  {
   fprintf(stderr, "Garbage in the conditions\n");
   return (void*)-1;
  }

  type = str2querytype(str);
  str = &(t[1]);
  while(str[0]==' ')str++;
  if(str[0] != '\'')
  {
   fprintf(stderr, "Syntax error in query conditions - missing quote\n");
   return (struct condition*)-1;
  }
  str++;
  t = str;
  while((t[0]!='\'')  &&
        (t[0]))t++;

  if(!t[0])
  {
   fprintf(stderr, "Syntax error in query conditions - missing closing quote\n");
   return (struct condition*)-1;
  }
  t[0] = '\0';

  /** @TODO String copying into condition can be optimized, not clear why this works. */
  condition = emalloc(sizeof(*condition) + strlen(str) + 1);
  condition->operator = operator;
  condition->type = type;
  memcpy(condition->value, str, strlen(str));
  condition->next = ret;
  ret = condition;
  str  = &(t[1]);

  /*
   * Trailing data - probably the next operator
   */
  if(str[0])
  {
   while(str[0]==' ' &&
         str[0])str++;

   if(str[0])
   {
     t = &(str[1]);
     while(t[0]!=' ' && 
           t[0])t++;
     if(!t[0])
     {
      fprintf(stderr, "Trailing garbage in query - '%s'\n", str);
      return (void*)-1;
     }
     t[0] = '\0';
     operator = str2querycondition(str);
     str = &(t[1]);
    }
  }
 }
 return ret;
}

static struct query *
compile_query (char * str)
{
 struct query * ret;
 char * a, * b;
 int type = -1;
 int uniq = 0;

 if(strncmp(str, "SELECT ", strlen("SELECT "))) /* RATS: ignore, string literal is nul terminated) */
  {
   fprintf(stderr, "Bad query : expected <SELECT >.\n");
   return NULL;
  }
 else
   str += strlen("SELECT "); /* RATS: ignore, string literal is nul terminated) */

 while(str[0] == ' ')
   str++;
 a = strchr(str, ' ');
 if(a)a[0]='\0';

 ret = emalloc(sizeof(*ret));
 ret->type[0] = type;
 ret->num  = 1;
 type = str2querytypes(str, ret);

 if(type < 0)
 {
  efree(&ret);
  fprintf(stderr, "'%s' - bad query type\n", str);
  return NULL;
 }


 if(!a)
 {
  fprintf(stderr, "'%s' - table not specified\n", str);
  efree(&ret);
  return NULL;
 }
 else
 {
  a++;
  if(strncmp(a, "FROM ", strlen("FROM "))) /* RATS: ignore, string literal is nul terminated) */
  {
   fprintf(stderr, "'%s' - \"FROM\" expected in '%s'\n", a, str);
   efree(&ret);
   return NULL;
  }
  a += strlen("FROM "); /* RATS: ignore, string literal is nul terminated) */
  ret->table = a;
  a = strchr(a, ' ');
  if(a)a[0] = '\0';
  ret->table = estrdup(ret->table); 
 }


 ret->uniq = uniq;
 if(a)
 {
  a++;
  while(a[0]==' ')a++;
  b = strchr(a, ' ');
  if(!b)
  {
   fprintf(stderr, "Garbage after query\n");
   efree(&ret);
   return NULL;
  }
  b[0]='\0';

  if(strcmp(a, "WHERE"))
  {
   fprintf(stderr, "Bad keyword after query ('%s')\n", a);
   efree(&ret);
   return NULL;
  }
  b = &(b[1]);
  ret->conditions = compile_conditions(b); 
  if(ret->conditions == (void*)-1)
   return NULL;
 }
 return ret;
}

static void
free_query (struct query * query)
{
 struct condition * conditions = query->conditions;
 while(conditions)
 {
  struct condition * next = conditions->next;
  efree(&conditions);
  conditions = next;
 }
 efree(&query);
}




/**
 * @brief Parse a line in a .nbe file and returns the records contained in it.
 * 
 * The input parameter entry is therefore changed in place (\nuls are added) and
 * the other parameters will point into entry.
 * 
 * @param entry The line to split at '|'s.
 * @param[host] subnet Will point to the beginning of the hostname string in entry.
 * 
 * @return Always 0.
 */
static int
__split_line (char * entry, char ** table, char** subnet, char** hostname,
              char** port, char** plugin_id, char** severity, char** data)
{
 char * t;
 *port = *plugin_id = *severity = *data = NULL;

 *table = entry;
 t = strchr(entry, '|');
 if(t)
  t[0] = '\0';
 else
  return 0;

 entry = &(t[1]);
 *subnet = entry;
 t = strchr(entry, '|');
 if(t)
  t[0] = '\0';
 else 
  return 0;

 entry = &(t[1]);
 *hostname = entry;
 t = strchr(entry, '|');
 if(t)
  t[0] = '\0';
 else 
  return 0;

 entry = &(t[1]);
 *port = entry;
 t = strchr(entry, '|');
 if(t)
  t[0] = '\0';
 else
  return 0;

 entry = &(t[1]);
 *plugin_id = entry;
 t = strchr(entry, '|');
 if(t) 
  t[0] = '\0';
 else
  return 0;

 entry = &(t[1]);
 *severity = entry;
 if(t)
  t[0] = '\0';
 else
  return 0;

 t = strchr(entry, '|');
 if(t)
  t[0] = '\0';
 else
  return 0;

  if (Context->is_severity_mapped)
    {
      const char * prio = severity_filter_apply (*hostname, *port, *plugin_id, *severity);
      if (prio) *severity = g_strdup (prio);
    }

 entry = &(t[1]);
 *data = entry;

#if 1
 if(entry)
 {
  char * t = strchr(entry, ';');
  while(t)
  {
   t[0] = '\n';
   t = strchr(t+1, ';');
  }
 }
#endif

 return 0;
}


/**
 * @brief Executes a compiled query and returns the result as a subset.
 *
 * This is god damn ugly and you probably want to use a real
 * sql backend if you want performances
 *
 */
static struct subset *
execute_query_flatfile (int be, struct query * query)
{
 struct subset * ret = NULL;
 static char *buf = NULL;
 static int   buf_sz = 0;

 if ( buf == NULL )
  {
    buf_sz = 1024*1024;
    buf = emalloc(buf_sz);
  }

#ifdef HAVE_MMAP
 if (backends[be].mmap) backends[be].cur_line = 0;
 else
#endif
 if (lseek(backends[be].fd, 0, SEEK_SET) < 0)
 {
  perror("lseek ");
 }

#ifdef HAVE_MMAP
 if(backends[be].fields)
 {
  char * requests[BE_NUM_FIELDS];
  u_short * lines;
  struct condition * conditions = query->conditions;
  int i;
  bzero(requests, sizeof(*requests)*BE_NUM_FIELDS);
  requests[0] = query->table;
  while(conditions)
  {
   requests[conditions->type] = conditions->value;
   conditions = conditions->next;
  }
  lines = requests2lines(be, requests);
  for (i=0; i<backends[be].num_lines; i++)
  {
   if(lines[i])
   {
   int j;
   char * val = NULL;
   char * table;
   char * subnet;
   char * hostname;
   char * port;
   char * plugin_id;
   char * severity;
   char * data;
   buf[0] = '\0';
   mmap_read_line_n(be, buf, buf_sz, i);
   __split_line(buf, &table, &subnet, &hostname, &port, &plugin_id, &severity, &data);
   for (j=0; j<query->num; j++)
    {
      switch(query->type[j])
        {
          case QUERY_TYPE_SUBNET :
              val = subnet;
              break;
          case QUERY_TYPE_HOST :
              val = hostname;
              break;
          case QUERY_TYPE_PORT :
              val =  port;
              break;
          case QUERY_TYPE_PLUGIN_ID :
              val = plugin_id;
              break;
          case QUERY_TYPE_SEVERITY :
              val = severity;
              break;
        case QUERY_TYPE_REPORT :
              val = data;
              break;
          default:
              fprintf(stderr, "data_mining.c: invalid switch value\n");
              break;
        }

      if(val)
        {
          if(!j)
            {
              ret = subset_add(ret, val);
              ret->id_line = i;
            }
          else ret = subset_add_again(ret,val);
        }
      }
   }
  }
  efree(&lines);
  return ret;
 }
#endif /* HAVE_MMAP */
 int curr_id_line = -1;
 while (read_line (be, buf, buf_sz ) > 0)
 {
   ++curr_id_line;
#ifdef DEBUG
   //printf ("Query Backend, Line (%d): %s \n", curr_id_line, buf);
#endif
  char * table;
  char * subnet;
  char * hostname;
  char * port;
  char * plugin_id;
  char * severity;
  char * data;
  int selected = 0;
  struct condition * conditions = query->conditions;
  __split_line(buf, &table, &subnet, &hostname, &port, &plugin_id, &severity, &data);

  if (strcmp(table, query->table))
   continue;

  // Check conditions
  if(!conditions)
   selected = 1;
  else
   while(conditions)
   {
    char * candidate = NULL;
    switch(conditions->type)
      {
        case QUERY_TYPE_SUBNET :
          candidate = subnet;
          break;
        case QUERY_TYPE_HOST :
          candidate = hostname;
          break;
        case QUERY_TYPE_PORT :
          candidate = port;
          break;
        case QUERY_TYPE_PLUGIN_ID :
          candidate = plugin_id;
          break;
        case QUERY_TYPE_SEVERITY :
          candidate = severity;
          break;

        case QUERY_TYPE_REPORT :
          candidate = data;
          break;
      }

    if (candidate && !strcmp (candidate, conditions->value))
      {
        selected = 1;
      }
    else if(conditions->operator == QUERY_OP_AND)
      {
        selected = 0;
        break;
      }

   conditions = conditions->next;
  }

  // Fill the resulting subset
  if (selected)
  {
   int i;
   char * val = NULL;

   for (i=0; i<query->num; i++)
    {
      switch(query->type[i])
        {
          case QUERY_TYPE_SUBNET :
              val = subnet;
              break;
          case QUERY_TYPE_HOST :
              val = hostname;
              break;
          case QUERY_TYPE_PORT :
              val =  port;
              break;
          case QUERY_TYPE_PLUGIN_ID :
              val = plugin_id;
              break;
          case QUERY_TYPE_SEVERITY :
              val = severity;
              break;
        case QUERY_TYPE_REPORT :
              val = data;
              break;
        }

      if ((i == 0) && (val == NULL))
        {
          goto continue_loop;
        }
      if (val)
          {
            if (query->uniq)
              {
                if (!i)
                  {
                    if (subset_in(ret, val))
                      {
                        goto continue_loop;
                      }
                    else
                      {
                        ret = subset_add (ret, val);
                        ret->id_line = curr_id_line;
                      }
                  }
                else ret = subset_add_again(ret, val);
              }
            else /* query-uniq */
              {
                if (!i)
                  {
                    ret = subset_add (ret, val);
                    ret->id_line = curr_id_line;
                  }
                else ret = subset_add_again(ret, val);
              }
          }
      }
   }
   continue;
continue_loop :
   ;
  }
  return ret;
}

/**
 * @brief Gets a subset with some values set, defined by line numbers.
 * 
 * Queries the results "table" only.
 * Subset will contain: host port oid severity data (in this order).
 * 
 * @param be           Backend- index to query.
 * @param line_nr_list List of line numbers in the nbe backend file (serve as a
 *                     workaround for primary keys).
 * 
 * @return Subset (list) with rows specified in the line_nr_list parameter, if
 *         accessible.
 */
struct subset*
report_query_all_by_line (int be, GSList* line_nr_list)
{
  struct subset* ret = NULL;
  static char* buf = NULL;
  static int   buf_sz = 0;

  if (buf == NULL)
    {
      buf_sz = 1024*1024;
      buf = emalloc(buf_sz);
    }

#ifdef HAVE_MMAP
  if (backends[be].mmap) backends[be].cur_line = 0;
  else
#endif
  if (lseek(backends[be].fd, 0, SEEK_SET) < 0)
    {
      perror("lseek ");
    }

#ifdef HAVE_MMAP
  if(backends[be].fields)
    {
      char * requests[BE_NUM_FIELDS];
      u_short * lines;
      //struct condition * conditions = query->conditions;
      int i;
      bzero (requests, sizeof(*requests)*BE_NUM_FIELDS);
      requests[0] = "results";
      lines = requests2lines(be, requests);
      for (i=0; i<backends[be].num_lines; i++)
        {
          // If lines[i] in list and exists
          if (g_slist_find (line_nr_list, GUINT_TO_POINTER (i)) != NULL
              && lines[i])
            {
              char * table;
              char * subnet;
              char * hostname;
              char * port;
              char * plugin_id;
              char * severity;
              char * data;
              buf[0] = '\0';
              mmap_read_line_n (be, buf, buf_sz, i);
              //printf ("Debug: Query result buffer: %s\n", buf);
              __split_line (buf, &table, &subnet, &hostname, &port, &plugin_id, &severity, &data);
              //if (!strcmp (table, "results"))
              ret = subset_add (ret, hostname);
              ret->id_line = i;

              ret = subset_add_again (ret, port);
              if (plugin_id != NULL)
                ret = subset_add_again (ret, plugin_id);
              if (severity != NULL)
                ret = subset_add_again (ret, severity);
              if (data != NULL)
                ret = subset_add_again (ret, data);
            }
        }

      efree(&lines);
      return ret;
    }
#endif /* HAVE_MMAP */
  int curr_id_line = -1;
  while (read_line (be, buf, buf_sz ) > 0)
    {
      ++curr_id_line;
#ifdef DEBUG
      printf ("Query Backend, Line (%d): %s \n", curr_id_line, buf);
#endif
      char * table;
      char * subnet;
      char * hostname;
      char * port;
      char * plugin_id;
      char * severity;
      char * data;
      __split_line(buf, &table, &subnet, &hostname, &port, &plugin_id, &severity, &data);

      if (strcmp(table, "results"))
        continue;

      if (g_slist_find (line_nr_list, GUINT_TO_POINTER (curr_id_line)) == NULL)
        continue;

      // If curr_id_line in list
      // Fill the resulting subset
      ret = subset_add (ret, hostname);
      ret->id_line = curr_id_line;

      // NULL guard these!
      ret = subset_add_again (ret, port);
      ret = subset_add_again (ret, plugin_id);
      ret = subset_add_again (ret, severity);
      ret = subset_add_again (ret, data);
    }

  return ret;
}

/** @copydoc report_query_all_by_line
 */
struct subset*
report_query_single_by_line (int be, unsigned int line_nr)
{
  GSList* list = NULL;
  list = g_slist_prepend (list, GUINT_TO_POINTER (line_nr));

  struct subset* sub_set = report_query_all_by_line (be, list);

  g_slist_free (list);
  return sub_set;
}


/**
 * @brief Builds a query struct str (printf-like) and executes it against a flat
 * @brief file.
 * 
 * Note: Supplied strings might get changed.
 * 
 * @param str Query in printf- format. Attention: may get changed.
 * 
 * @return Result (subset) of the query (not sure if it can be NULL) or NULL if failed.
 */
struct subset  *
query_backend (int be, char* str, ...)
{
  struct query * query;
  struct subset * ret;
  va_list param;
  int sz = 8192;
  int r;
  char * ptr = emalloc(sz);
  va_start(param, str);
  for(;;)
    {
      r = vsnprintf(ptr, sz, str, param);
      if (r >= 0 && r < sz) break;
      sz = r > sz ? r + 1 : sz * 2;
      ptr = realloc(ptr, sz);
    }

  query = compile_query (ptr);

  va_end (param);
  efree (&ptr);

  if (!query)
    return NULL;

  ret = execute_query_flatfile (be, query);
  free_query (query);
  return ret;
}
