/* Nessus
 * Copyright (C) 1998 - 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.
 */

/** @file
 * Mainly functions to parse messages coming from the server and undertake
 * appropriate actions.
 */

#include <includes.h>
#include "nessus_i18n.h"

#include "auth.h"
#include "error_dlg.h"
#include "nessus_plugin.h"
#include "context.h"
#include "parser.h"
#include "parseutils.h"
#include "monitor_dialog.h"
#include "backend.h"
#include "globals.h"

#define TIMER_HOST_START 1
#define TIMER_HOST_END   2
#define TIMER_SCAN_START 3
#define TIMER_SCAN_END   4

static int 
is_mac_addr (char* host)
{
 int i;
 if(strlen(host) == 17) /* 12 numbers + 5 */
 {
  for(i=0;i<6;i++, host+=3)
  { 
   int j;
   for(j=0;j<2;j++)
   {
    if(!((host[j] >= '0' && host[j] <= '9') ||
       (host[j] >= 'a' && host[j] <= 'f')))
        return 0;
   }
   host+=3;
  }
 }
 else return 0;
 
 return 1;
}


/**
 * nessusd does not convert the subnet by itself, so we create
 * this record on this fly
 */
static char *
__host2subnet (char* host)
{
 static char subnet[1024];
 struct in_addr ia;
 int mac = 0;
 
 subnet[0] = '\0';
 
 if((inet_aton(host, &ia) == 0) &&
    (!(mac = is_mac_addr(host))))
 {
  char * t;
  /* Not an IP nor a MAC addr */
  t = strchr(host, '.');
  if(t)
	++t;
  else
	t = host;
   strncpy(subnet, t, sizeof(subnet) - 1);
 }
 else
 {
   if(mac)
   {
    /* This is a MAC address. In that case, the 'subnet'
     * are the first three bytes (manufacturer id) */
    char * t = strchr(host,'.');
    int i;
    for(i=0;i<2;i++)
        {
	if(!t)break;
    	t = strchr(t+1, '.');
       }
    if(t)
    {
     t[0] = '\0';
     strncpy(subnet, host, sizeof(subnet) - 1);
     t[0] = '.';
    }  
    else strncpy(subnet, host, sizeof(subnet) - 1);
   }
   else
   {
     /* This is an IP */
     char * t = strrchr(host, '.');
     if(t)t[0] = '\0';
     strncpy(subnet, host, sizeof(subnet) - 1);
     if(t)t[0] = '.';
  }
 }
 return subnet;
}


/**
 * @brief Convert message type into a human readable string
 * 
 * Return value is the English name for the message type.
 * 
 * @param name Message type
 * @return Message type string
 */
char *
priority_type_to_str (int type)
{
  switch(type)
    {
    case MSG_HOLE :
      return "Security Hole";
      break;
    case MSG_INFO :
      return "Security Warning";
      break;
    case MSG_NOTE :
      return "Security Note";
      break;
    case MSG_FALSE :
      return "False Positive";
      break;
    case MSG_LOG :
      return "Log Message";
      break;
    case MSG_DEBUG :
      return "Debug Message";
      break;
    default :
      fprintf(stderr, "received unknown message type (%d)\n", type);
      return NULL;
    }
}


/**
 * @brief Convert internal message type into integer value
 * 
 * Return value is one of MSG_HOLE, MSG_INFO, MSG_NOTE, MSG_FALSE, MSG_TIME,
 * MSG_STAT, MSG_PORT, MSG_ERROR, MSG_FINISHED, MSG_BYE, MSG_LOG, MSG_DEBUG
 * and -1 for unknown message type.
 * 
 * @param name Internal message type name
 * @return Message type code or -1 if unknown.
 */
int
priority_name_to_type (char *name)
{
#ifdef DEBUG
 fprintf(stderr, "%s:%d type : %s\n", __FILE__, __LINE__, name);
#endif
 if(!strcmp(MSG_HOLE_STR, name))return(MSG_HOLE);
 if(!strcmp(MSG_INFO_STR, name))return(MSG_INFO);
 if(!strcmp(MSG_NOTE_STR, name))return(MSG_NOTE);
 if(!strcmp(MSG_FALSE_STR, name))return(MSG_FALSE);
 if(!strcmp(MSG_TIME_STR, name))return(MSG_TIME);
 if(!strcmp(MSG_STAT_STR, name))return(MSG_STAT);
 if(!strcmp(MSG_PORT_STR, name))return(MSG_PORT);
 if(!strcmp(MSG_ERROR_STR, name))return(MSG_ERROR);
 if(!strcmp(MSG_FINISHED_STR, name))return(MSG_FINISHED);
 if(!strcmp(MSG_BYE_STR, name))return(MSG_BYE);
 if(!strcmp(MSG_LOG_STR, name))return(MSG_LOG);
 if(!strcmp(MSG_DEBUG_STR, name))return(MSG_DEBUG);
 return(-1);
}

/**
 * Parse a configuration of multiple of "<|> ldap (389/tcp) <|> INFO <|> NOTE <|>"
 * and return the new priority as integer if the line matches the current script.
 *
 * @param override Override value from the openvasrc configuration
 * @param port     Port specification for this script
 * @param priority Server-side priority for this script
 *
 * @return New priority on success, on error or mismatch 0.
 */
int
parse_calc_priority (char* override, char* port, int priority)
{
  char * confport = NULL;
  char * confprio;
  int oldprio, newprio;
  char * pivot;
  int prio = 0;

  pivot = override;

  while (prio == 0 && strlen(pivot) > 5) {
    confport = parse_separator(override);

    if (!confport)
      return 0;

    pivot += strlen(confport) + 5;
    confprio = parse_separator(pivot);

    if (!confprio) {
      efree(&confport);
      return 0;
    }

    if ((oldprio = priority_name_to_type(confprio)) == -1) {
      efree(&confport);
      efree(&confprio);
      return 0;
    }

    pivot += strlen(confprio) + 5; 
    efree(&confprio);

    confprio = parse_separator(pivot);

    if (!confprio) {
      efree(&confport);
      return 0;
    }

    if ((newprio = priority_name_to_type(confprio)) == -1) {
      efree(&confport);
      efree(&confprio);
      return 0;
    }

    pivot += strlen(confprio) + 5; 
    efree(&confprio);

    if (!strcmp(confport, port) && oldprio == priority)
      prio = newprio;
    else
      efree(&confport);
  }

  if (confport)
    efree(&confport);
  
  return prio;
}

/**
 * @brief Adjust the priority of a script's output and adds information about
 * @brief client-side adjusted priority in the text.
 *
 * @param script_id      Unified script id (OID).
 * @param context        Current context the script is run in.
 * @param priority[out]  Server-side priority for this script.
 * @param port           Port specification for this script.
 * @param data           Output returned from the script.
 *
 * @return adjusted script output or NULL if no override was found or an error occurred
 * @return priority adjusted priority for later processing
 */
char *
parse_modify_data (char * host, char * script_id, struct context * context, int * priority,
                   char* port, char * data)
{
  char *prio_old, *prio_new;
  char *format = _("\nPriority changed from %s to %s\n");
  char *tmp;
  size_t len;
  struct arglist *priorities = NULL;
  char *override;
  char *prefkey;
  int prio;

  prefkey = g_strdup_printf ("PLUGINS_PRIORITIES_%s", host);

  if ((priorities = arg_get_value(context->prefs, prefkey)) == NULL) {
    g_free (prefkey);
    return NULL;
  }

  g_free (prefkey);

  if ((override = arg_get_value(priorities, script_id)) == NULL)
    return NULL;

  if ((prio = parse_calc_priority(override, port, *priority))) {
    if ((prio_old = priority_type_to_str(*priority)) == NULL)
      return NULL;

    if ((prio_new = priority_type_to_str(prio)) == NULL)
      return NULL;

    *priority = prio;

    /* strlen* + '\0' - 2*"%s" */
    len = strlen(data) + strlen(format) + strlen(prio_old) + strlen(prio_new) -3;

    if ((tmp = realloc(data, len)) == NULL)
      return data;

    data = tmp;
    tmp += strlen(data);
    sprintf(tmp, format, prio_old, prio_new);
  }

  return data;
}

/**
 * @brief Feed errors from the Nessus Server to gettext and display them.
 */
void
parse_server_error (char* servmsg)
{
  char *msg = parse_separator(servmsg);
  char *msg1, *msg2;

  /* msg2 contains the following lines of the server error.
   * Currently this can only be the list of rejected hosts,
   * so don't translate them.  */
  if((msg2 = strchr(msg, ';')))
  {
    char *t;

    msg2[0] = '\0';
    msg2++;
    while((t = strchr(msg2, ';')))
      t[0]='\n';
  }

  /* Keep these in sync with the messages in ../nessusd/attack.c */
  if(!strncmp("E001 -", msg, 6))
    msg1 = _("Invalid port range");
  else if(!strncmp("E002 -", msg, 6))
    msg1 = _("These hosts could not be tested because you"
	     " are not allowed to do so:");
  else
    msg1 = msg;

  if(msg2)
    show_error("%s\n%s", msg1, msg2);
  else
    show_error("%s", msg1);
  efree(&msg);
}

/**
 * @brief Analyzes a message received from the server, and performs what must
 * @brief must be done depending on the server message.
 * 
 * @return MSG_STAT2 if its "just" a status update
 */
int
parse_server_message (struct context* context, char * message, int backend,
                      char * humanmsg)
{
 char message_type[255];
 char * buf;
 char * t;
 int type;
 
 if(!strncmp(message, "s:", 2))
  return MSG_STAT2;
 else
  {
  t = strstr(message, "SERVER <|> ");
  if(!t)return(-1);
  buf = strstr(message, "<|>");
  buf+=4;
  t = strstr(buf, " <|>");
  if(t)
  {
  t[0]=0;
  }
 strncpy(message_type,buf, sizeof(message_type) - 1);
 if(t)t[0]=' ';
 type = priority_name_to_type(message_type);
 }
 
 switch(type)
 {
  case MSG_TIME:
   {
	char * msg = parse_separator(t);
	int type = 0;
	int len = 0;
	
	if(!strcmp(msg, "HOST_START")){
			len = strlen(msg);
			type = TIMER_HOST_START;
			}
	else if(!strcmp(msg, "HOST_END")){
			type = TIMER_HOST_END;
			len = strlen(msg);
			}

	if(type)
	{
	 char * host = parse_separator(t+len+3);
	 char * date = t+strlen(" <|> ")+len+strlen(host)+10;
	 char* e;

         e = strrchr(t, '<');
         if(e != NULL)
         {
               e--;e[0] = '\0';
         }


	 switch(type)
	 {
	  case TIMER_HOST_START:
	  	backend_insert_timestamps(backend, host, "host_start", date); 
		break;
	  case TIMER_HOST_END:
	  	backend_insert_timestamps(backend, host, "host_end", date); 
		break;
	 }
	}
	
	else
	{
	 if(!strcmp(msg, "SCAN_START")) {
		type = TIMER_SCAN_START;
		len = strlen(msg);
		}
	 else if(!strcmp(msg, "SCAN_END"))
	 	{
		type = TIMER_SCAN_END;
		len = strlen(msg);
		}
	
	 if(type)
	 {
	  char* date = &t[5+len+strlen(" <|> ")];
	  char* e;
	  
	  e = strrchr(t, '<');
	  if(e != NULL) 
	  	{
		e--;e[0] = '\0';
		}
	  switch(type)
	  {
	    case TIMER_SCAN_START:
	   	backend_insert_timestamps(backend, "", "scan_start", date);
		break;
	   case TIMER_SCAN_END:
	   	backend_insert_timestamps(backend, "", "scan_end", date);
		break;
	   }
	  }
	}
	
	efree(&msg);
	}
	break;
	
  case MSG_STAT2 :
  	return(MSG_STAT2);
	break;
  case MSG_ERROR :
	parse_server_error(t);
	return(MSG_ERROR);
  	break;
  case MSG_PORT :
  	parse_host_add_port(backend, t, humanmsg);
  	return(MSG_PORT);
  	break;
  case MSG_HOLE :
     	humanmsg[0]=0;
  	parse_host_add_data(backend, context, t, MSG_HOLE);
  	return(MSG_HOLE);
  	break;
  case MSG_INFO :
  	humanmsg[0]=0;
  	parse_host_add_data(backend, context, t, MSG_INFO);
  	break;
  case MSG_NOTE :
  	humanmsg[0]=0;
  	parse_host_add_data(backend, context, t, MSG_NOTE);
  	break;
  case MSG_LOG :
  	humanmsg[0]=0;
  	parse_host_add_data(backend, context, t, MSG_LOG);
  	break;
  case MSG_DEBUG :
  	humanmsg[0]=0;
  	parse_host_add_data(backend, context, t, MSG_DEBUG);
  	break;
  case MSG_STAT :
	{
	int tl = strlen(message_type);
	int l = strlen(buf + tl);
  	strncpy(humanmsg, buf+tl, l);
	humanmsg[l] = '\0';
  	return(MSG_STAT);
  	break;
	}
  case MSG_FINISHED :
        {
	 if(!F_quiet_mode)
	 {
	 char * v = strstr(t, " <|> SERVER");
         int t_len;
	 if(v)v[0]=0;
	 else return 0;
	 v = strstr(t, " <|> ");
	 if(v)t = v+strlen(" <|> ");
	 t_len = strlen(t);
	 strncpy(humanmsg, t, t_len);
         humanmsg[t_len] = '\0';
	 return MSG_FINISHED;
	 }
	break;	
	 }
  case MSG_BYE :
        humanmsg[0]=0;
  	return(MSG_BYE);
  	break;
  }
 return(-1);
}

/**
 * @brief The server has sent a STATUS message.
 */
void 
parse_nessusd_status (char * servmsg, char ** host, char ** action,
                      char ** current, int * max)
{
	char * t1,*t2;
	
	t1 = parse_separator(servmsg);if(!t1)return;
	t2 = parse_separator(servmsg+strlen(t1)+3);
	if(!t2){
		efree(&t1);
		return;
		}
		
		
	*host = emalloc(strlen(t1) + 1);
	strncpy(*host, t1, strlen(t1));
	
	*action = emalloc(strlen(t2) + 1);
        strncpy(*action, t2, strlen(t2));

	efree(&t2);
        t2 = parse_separator(servmsg+strlen(t1)+3);
	efree(&t1);
	if(!t2)
	 return;
	 
	t1 = strchr(t2, '/');
	if(t1)
	{
	t1[0]=0;
	}
	*current = strdup(t2);
	
	if(t1)
	{
	t1+=sizeof(char);
	*max = atoi(t1);
	}
	efree(&t1);
	efree(&t2);
}

void
parse_nessusd_short_status (char * msg, char ** host, char ** action,
                            char ** current, int * max)
{
 char * t;
 static char portscan[] = "portscan";
 static char attack[]   = "attack";
  /* The short status is : action:hostname:current:max (e.g. s:a:localhost:44:102)*/
  if(msg[0]=='p')
   *action = strdup(portscan);
  else
   *action  = strdup(attack);
  
  msg = msg + 2; 
  t = strchr(msg, ':');
  if(!t)
   return; 
  t[0] = 0;
  *host = strdup(msg);
  
  msg = t + sizeof(char);
  t = strchr(msg, ':');
  
  if(!t)
   return;
  t[0]=0;
  *current = strdup(msg);
  msg = t + sizeof(char);
  *max = atoi(msg);
}

/**
 * @brief The server has sent a PORT message.
 */
void
parse_host_add_port (int backend, char* servmsg, char* humanmsg)
{
 char * port;
 char * hostname;
 char * subnet;

 hostname = parse_separator(servmsg);if(!hostname)return;
 port = parse_separator(servmsg+strlen(hostname)+3);
 if(!port){efree(&hostname); return;}
 subnet = __host2subnet(hostname);
  backend_insert_report_port(backend, subnet, hostname, port);
#ifdef DEBUG
  fprintf(stderr, "Port %s opened on %s\n", port, hostname);
#endif

  sprintf(humanmsg, "Port %s opened on %s", port, hostname);
  efree(&hostname);
  efree(&port);
}

/**
 * @brief Adds an entry into the backend to report that the server has sent a
 * @brief HOLE, INFO, NOTE, LOG or DEBUG message.
 *
 * @param backend   The backend index.
 * @param servermsg The server message as a string.
 * @param type      One of {MSG_HOLE, MSG_INFO, MSG_NOT, MSG_LOG, MSG_DEBUG}.
 */
void
parse_host_add_data (int backend, struct context * context, char * servmsg,
                     int type)
{
 char * port;
 char * data;
 char * hostname;
 char * subnet;
 char * msgt;
 char * script_id;
 char * old;

 msgt = priority_type_to_str (type);

 if (msgt == NULL)
     return;

 hostname = parse_separator(servmsg);
 if(!hostname){
	return;
	}
 old = servmsg + strlen(hostname) + 5;
 port = parse_separator(old);
 if(!port){
	efree(&hostname);
	return;
 	}
 old += strlen(port) + 5; 
 data = parse_separator(old);
 if(!data){
 	efree(&hostname);
	efree(&port);
	return;
	}
 old += strlen(data) + 5; 
 script_id = parse_separator(old); 
 if(!script_id){
 	efree(&hostname);
	efree(&port);
	efree(&data);
	return;
	}
#if 0
 if ((msgt = priority_type_to_str(type)) == NULL)
   return;

 if ((data = parse_modify_data(hostname, script_id, context, &type, port, data)) == NULL)
   return;

 if ((msgt = priority_type_to_str(type)) == NULL)
   return;
#endif

 old = data;
 data = rmslashes(old);
 efree(&old);
 
#ifdef NOT_READY
   if( (type==MSG_INFO) && !strncmp(data, "Traceroute", strlen("Traceroute")))
   {
    netmap_add_data(data);
   }
#endif
   subnet = __host2subnet(hostname);
   backend_insert_report_data(backend, subnet, hostname, port, script_id,
		msgt, data);

#ifdef DEBUG
  fprintf(stderr,"data for %s (port %s) [type : %d] : \n%s\n", hostname, port, type, data);
#endif

  efree(&script_id);
  efree(&hostname);
  efree(&port);
  efree(&data);
}
 

/**
 * @return Pointer to the location after the ' <|> ' symbol in str or NULL if
 *         no ' <|> ' found.
 */
char *
parse_symbol(char *str)
{
 char * s = str;
 
 while(s)
 {
 s = strchr(s, '|');
 if(!s)return(NULL);
 if((s[1]=='>')&&(s-1)[0]=='<')
 	{
 	return s+3;
 	}
 s++;
 }
 return NULL;
}

/**
 * @brief In the standard case, returns content between two separators (\<|\>)
 * @brief in a string.
 * 
 * Returns a copy of the string between the first two ' <|> 's, or from the 
 * first ' <|> ' to the end of string str or NULL if str is empty or does not
 * contain a ' <|> '.
 * 
 * @param str String to parse.
 */
char *
parse_separator(char* str)
{
 char * s_1, *s_2;
 char * ret;
 
 s_1 = parse_symbol(str);
 if(!s_1 || s_1[0] == '\0')
   return NULL;
   
 s_2 = parse_symbol(s_1);
 if(!s_2)
  {
  ret = emalloc(strlen(s_1));
  strncpy(ret, s_1, strlen(s_1)-1);
  }
 else
 {
  int c;
  
  s_2 = s_2 - 4;
  c = s_2[0];
  s_2[0] = 0;
  ret = emalloc(strlen(s_1));
  strncpy(ret, s_1, strlen(s_1)-1);
  s_2[0]=c;
  }
  return ret;
}
