/***************************************************************************
 *
 * Copyright (c) 2000-2001 BalaBit IT Ltd, Budapest, Hungary
 *
 * 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, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: telnetpolicy.c,v 1.9.2.1 2001/09/19 19:24:41 bazsi Exp $
 *
 ***************************************************************************/
#include <zorp/zorp.h>
#include <zorp/policy.h>
#include <zorp/thread.h>
#include <zorp/zpython.h>
#include <zorp/log.h>

#include <string.h>
#include <ctype.h>
#include <stdlib.h>

#include "telnet.h"

#define TELNET_POLICY "telnet.policy"
#define TELNET_DEBUG "telnet.debug"

gboolean
telnet_hash_get_type(ZPolicyObj *tuple, guint *filter_type)
{
  ZPolicyObj *tmp;

  if (!z_policy_seq_check(tuple))
    {
      if (z_policy_var_parse(tuple, "i", filter_type))
        return TRUE;
      return FALSE;
    }

  tmp = z_policy_seq_getitem(tuple, 0);
  if (!z_policy_var_parse(tmp, "i", filter_type))
    {
      /* policy syntax error */
      z_policy_var_unref(tmp);
      return FALSE;
    }
  z_policy_var_unref(tmp);
  return TRUE;
}

guint
telnet_policy_option(TelnetProxy *self)
{
  guint res;
  ZPolicyObj *pol_res;
  ZPolicyObj *tmp;
  ZPolicyObj *command_where = NULL;
  guint command_do;
  gchar lookup_str[10];
  gchar *keys[1];

  z_proxy_enter(self);
  
  z_proxy_log(self, TELNET_DEBUG, 8, "policy option negotiation check");

  g_snprintf(lookup_str, sizeof(lookup_str), "%d", self->opneg_option[self->ep]);
  keys[0] = lookup_str;
  
  tmp = z_dim_hash_table_search(self->telnet_policy, 1, keys);

  if (!tmp)
    {
      z_proxy_leave(self);
      z_proxy_log(self, TELNET_POLICY, 1, "option %s not found in policy hash, dropping", lookup_str);
      return TELNET_CHECK_DROP;
    }

  z_policy_lock(self->super.thread);

  if (!telnet_hash_get_type(tmp, &command_do))
    {
      z_proxy_log(self, TELNET_POLICY, 1, "policy type invalid!");
      z_policy_unlock(self->super.thread);
      z_proxy_leave(self);
      return TELNET_CHECK_ABORT;
    }
  z_policy_unlock(self->super.thread);

  switch (command_do)
    {
    case TELNET_OPTION_DROP:
      z_proxy_log(self, TELNET_POLICY, 1, "policy denied option");
      res = TELNET_CHECK_DROP;
      break;
    case TELNET_OPTION_ACCEPT:
      z_proxy_log(self, TELNET_POLICY, 6, "policy accepted option");
      res = TELNET_CHECK_OK;
      break;
    case TELNET_OPTION_POLICY:
      z_policy_lock(self->super.thread);
      if (!z_policy_var_parse(tmp, "(iO)", &command_do, &command_where))
        {
	  z_proxy_log(self, TELNET_POLICY, 1, "Cannot parse policy line for option: %d", self->opneg_option[self->ep]);
	  res = TELNET_CHECK_ABORT;
	}
      else 
        {
	  pol_res = z_policy_call_object(command_where, z_policy_var_build("(i)", &self->opneg_option[self->ep]));
	  if (pol_res == NULL)
	    {
	      z_proxy_log(self, TELNET_POLICY, 1, "Error in policy calling for option: %d", self->opneg_option[self->ep]);
	      res = TELNET_CHECK_ABORT;
	    }
	  else
	    {
	      if (!z_policy_var_parse(pol_res, "i", &res))
	        {
		  z_proxy_log(self, TELNET_POLICY, 1, "Can't parse return code for option: %d", self->opneg_option[self->ep]);
		  res = TELNET_CHECK_ABORT;
		}
	      else
		switch (res)
		  {
		  case Z_OK:
                    z_proxy_log(self, TELNET_POLICY, 6, "policy function accepted option");
                    res = TELNET_CHECK_OK;
		    break;
		  case Z_UNSPEC:
		  case Z_DENY:
		  case Z_DROP:
                    z_proxy_log(self, TELNET_POLICY, 1, "policy function denied option");
		    res = TELNET_CHECK_DROP;
		    break;
		  case TELNET_OPTION_REJECT:
		    res = TELNET_CHECK_REJECT;
		    break;
		  case Z_ABORT:
		  default:
                    z_proxy_log(self, TELNET_POLICY, 1, "policy function aborted session");
		    res = TELNET_CHECK_ABORT;
		    break;
		  }
	    }
	}
      z_policy_unlock(self->super.thread);
      break;
    case TELNET_OPTION_REJECT:
      z_proxy_log(self, TELNET_POLICY, 1, "policy rejected option");
      res = TELNET_CHECK_REJECT;
      break;
    case TELNET_OPTION_ABORT:
    default:
      z_proxy_log(self, TELNET_POLICY, 1, "policy aborted session.");
      res = TELNET_CHECK_ABORT;
      break;
    }
  
  z_proxy_leave(self);

  return res;
}

guint
telnet_policy_suboption(TelnetProxy *self, guchar command, gchar *name, gchar *value)
{
  guint res;
  ZPolicyObj *pol_res;
  ZPolicyObj *tmp;
  ZPolicyObj *command_where = NULL;
  guint command_do;
  gchar lookup_str[2][10];
  gchar *keys[2];

  z_proxy_enter(self);
  
  z_proxy_log(self, TELNET_DEBUG, 8, "policy suboption negotiation check");

  g_snprintf(lookup_str[0], sizeof(lookup_str[0]), "%d", self->opneg_option[self->ep]);
  g_snprintf(lookup_str[1], sizeof(lookup_str[1]), "%d", command);
  keys[0] = lookup_str[0];
  keys[1] = lookup_str[1];
  
  tmp = z_dim_hash_table_search(self->telnet_policy, 2, keys);

  if (!tmp)
    {
      z_proxy_leave(self);
      z_proxy_log(self, TELNET_POLICY, 1, "option not found in policy hash, dropping; command=`%s', option=`%s'", lookup_str[1], lookup_str[0]);
      return TELNET_CHECK_DROP;
    }

  z_policy_lock(self->super.thread);

  if (!telnet_hash_get_type(tmp, &command_do))
    {
      z_proxy_log(self, TELNET_POLICY, 1, "policy type invalid!");
      z_policy_unlock(self->super.thread);
      z_proxy_leave(self);
      return TELNET_CHECK_ABORT;
    }
  z_policy_unlock(self->super.thread);

  switch (command_do)
    {
    case TELNET_OPTION_DROP:
      z_proxy_log(self, TELNET_POLICY, 1, "policy denied suboption");
      res = TELNET_CHECK_DROP;
      break;
    case TELNET_OPTION_ACCEPT:
      z_proxy_log(self, TELNET_POLICY, 1, "policy accepted suboption");
      res = TELNET_CHECK_OK;
      break;
    case TELNET_OPTION_POLICY:
      z_policy_lock(self->super.thread);
      if (!z_policy_var_parse(tmp, "(iO)", &command_do, &command_where))
        {
	  z_proxy_log(self, TELNET_POLICY, 1, "Cannot parse policy line for option: %d", self->opneg_option[self->ep]);
	  res = TELNET_CHECK_ABORT;
	}
      else 
        {
	  /* call Python method with appropriate parameters */
	  switch (self->opneg_option[self->ep])
	    {
	      case TELNET_OPTION_TERMINAL_TYPE:
	      case TELNET_OPTION_TERMINAL_SPEED:
	      case TELNET_OPTION_X_DISPLAY_LOCATION:
	      case TELNET_OPTION_NEW_ENVIRON:
	      case TELNET_OPTION_NAWS:
		pol_res = z_policy_call_object(command_where, z_policy_var_build("(iss)", &self->opneg_option[self->ep], name, value));
		break;
	      default:
		pol_res = z_policy_call_object(command_where, z_policy_var_build("(i)", &self->opneg_option[self->ep]));
		break;
	    }

	  if (pol_res == NULL)
	    {
	      z_proxy_log(self, TELNET_POLICY, 1, "Error in policy calling for option: %d", self->opneg_option[self->ep]);
	      res = TELNET_CHECK_ABORT;
	    }
	  else
	    {
	      if (!z_policy_var_parse(pol_res, "i", &res))
	        {
		  z_proxy_log(self, TELNET_POLICY, 1, "Can't parse return code for option: %d", self->opneg_option[self->ep]);
		  res = TELNET_CHECK_ABORT;
		}
	      else
		switch (res)
		  {
		  case Z_OK:
                    z_proxy_log(self, TELNET_POLICY, 1, "policy function accepted suboption");
                    res = TELNET_CHECK_OK;
		    break;
		  case Z_UNSPEC:
		  case Z_DENY:
		  case Z_REJECT:
		  case Z_DROP:
                    z_proxy_log(self, TELNET_POLICY, 1, "policy function denied suboption");
		    res = TELNET_CHECK_DROP;
		    break;
		  case Z_ABORT:
		  default:
                    z_proxy_log(self, TELNET_POLICY, 1, "policy function aborted suboption");
		    res = TELNET_CHECK_ABORT;
		    break;
		  }
	    }
	}
      z_policy_unlock(self->super.thread);
      break;
    case TELNET_OPTION_ABORT:
    default:
      z_proxy_log(self, TELNET_POLICY, 1, "policy aborted session");
      res = TELNET_CHECK_ABORT;
      break;
    }
  
  z_proxy_leave(self);

  return res;
}

