/**
 * Remove a mapping created with (mini)upnpc.  Calling
 * this function will give 'upnpc' 1s to remove tha mapping,
 * so while this function is non-blocking, a task will be
 * left with the scheduler for up to 1s past this call.
 *
 * @param mini the handle
 */
void
GNUNET_NAT_mini_map_stop (struct GNUNET_NAT_MiniHandle *mini)
{
  char pstr[6];

  if (NULL != mini->refresh_task)
  {
    GNUNET_SCHEDULER_cancel (mini->refresh_task);
    mini->refresh_task = NULL;
  }
  if (NULL != mini->refresh_cmd)
  {
    GNUNET_OS_command_stop (mini->refresh_cmd);
    mini->refresh_cmd = NULL;
  }
  if (NULL != mini->map_cmd)
  {
    GNUNET_OS_command_stop (mini->map_cmd);
    mini->map_cmd = NULL;
  }
  if (GNUNET_NO == mini->did_map)
  {
    GNUNET_free (mini);
    return;
  }
  mini->ac (mini->ac_cls,
	    GNUNET_NO,
            (const struct sockaddr *) &mini->current_addr,
            sizeof (mini->current_addr),
            GNUNET_NAT_ERROR_SUCCESS);
  /* Note: oddly enough, deletion uses the external port whereas
   * addition uses the internal port; this rarely matters since they
   * often are the same, but it might... */
  GNUNET_snprintf (pstr,
                   sizeof (pstr),
                   "%u",
                   (unsigned int) ntohs (mini->current_addr.sin_port));
  LOG (GNUNET_ERROR_TYPE_DEBUG,
       "Unmapping port %u with UPnP\n",
       ntohs (mini->current_addr.sin_port));
  mini->unmap_cmd 
    = GNUNET_OS_command_run (&process_unmap_output,
			     mini,
			     UNMAP_TIMEOUT,
                             "upnpc",
			     "upnpc",
			     "-d",
			     pstr,
                             mini->is_tcp ? "tcp" : "udp",
			     NULL);
}
static void
exec_cmd_proc (void *cls, const char *line)
{
  struct SysmonProperty *sp = cls;
  unsigned long long tmp;
  GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Property output: `%s'\n", line);
  if (NULL == line)
  {
      GNUNET_OS_command_stop (sp->cmd_exec_handle);
      sp->cmd_exec_handle = NULL;
      return;
  }

  switch (sp->value_type) {
    case v_numeric:
      if (1 != sscanf (line, "%llu", &tmp))
      {
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Command output was not a numerical value: `%s'\n", line);
        return;
      }
      break;
    case v_string:
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "NOT IMPLEMENTED\n");
      break;
    default:
      break;

  }
  sp->num_val = tmp;

  GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Property output: `%s'\n", line);
  put_property (sp);


}
/**
 * Run "upnpc -l" to find out if our mapping changed.
 *
 * @param cls the 'struct GNUNET_NAT_MiniHandle'
 */
static void
do_refresh (void *cls)
{
  struct GNUNET_NAT_MiniHandle *mini = cls;
  int ac;

  mini->refresh_task 
    = GNUNET_SCHEDULER_add_delayed (MAP_REFRESH_FREQ,
				    &do_refresh,
				    mini);
  LOG (GNUNET_ERROR_TYPE_DEBUG,
       "Running `upnpc' to check if our mapping still exists\n");
  mini->found = GNUNET_NO;
  ac = GNUNET_NO;
  if (NULL != mini->map_cmd)
  {
    /* took way too long, abort it! */
    GNUNET_OS_command_stop (mini->map_cmd);
    mini->map_cmd = NULL;
    ac = GNUNET_YES;
  }
  if (NULL != mini->refresh_cmd)
  {
    /* took way too long, abort it! */
    GNUNET_OS_command_stop (mini->refresh_cmd);
    mini->refresh_cmd = NULL;
    ac = GNUNET_YES;
  }
  mini->refresh_cmd 
    = GNUNET_OS_command_run (&process_refresh_output,
			     mini,
			     MAP_TIMEOUT,
                             "upnpc",
			     "upnpc",
			     "-l",
			     NULL);
  if (GNUNET_YES == ac)
    mini->ac (mini->ac_cls,
              GNUNET_SYSERR,
              NULL,
	      0,
              GNUNET_NAT_ERROR_UPNPC_TIMEOUT);
}
/**
 * Process output from our 'unmap' command.
 *
 * @param cls the `struct GNUNET_NAT_MiniHandle`
 * @param line line of output, NULL at the end
 */
static void
process_unmap_output (void *cls,
		      const char *line)
{
  struct GNUNET_NAT_MiniHandle *mini = cls;

  if (NULL == line)
  {
    LOG (GNUNET_ERROR_TYPE_DEBUG,
         "UPnP unmap done\n");
    GNUNET_OS_command_stop (mini->unmap_cmd);
    mini->unmap_cmd = NULL;
    GNUNET_free (mini);
    return;
  }
  /* we don't really care about the output... */
}
static void
exec_cmd (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
{
  struct SysmonProperty *sp = cls;
  GNUNET_assert (NULL != sp->cmd);

  if (NULL != sp->cmd_exec_handle)
  {
    GNUNET_OS_command_stop (sp->cmd_exec_handle);
    sp->cmd_exec_handle = NULL;
    GNUNET_break (0);
  }
  GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Property `%s': command `%s' `%s'\n", sp->desc, sp->cmd, sp->cmd_args);
  if (NULL == (sp->cmd_exec_handle = GNUNET_OS_command_run (&exec_cmd_proc, sp,
      GNUNET_TIME_UNIT_SECONDS,
      sp->cmd, sp->cmd,
      sp->cmd_args,
      NULL)))
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Property `%s': command `%s' failed\n", sp->desc, sp->cmd);
}
/**
 * Process the output from the 'upnpc -r' command.
 *
 * @param cls the `struct GNUNET_NAT_MiniHandle`
 * @param line line of output, NULL at the end
 */
static void
process_map_output (void *cls,
                    const char *line)
{
  struct GNUNET_NAT_MiniHandle *mini = cls;
  const char *ipaddr;
  char *ipa;
  const char *pstr;
  unsigned int port;

  if (NULL == line)
  {
    GNUNET_OS_command_stop (mini->map_cmd);
    mini->map_cmd = NULL;
    if (GNUNET_YES != mini->did_map)
      mini->ac (mini->ac_cls,
                GNUNET_SYSERR,
                NULL,
		0,
                GNUNET_NAT_ERROR_UPNPC_PORTMAP_FAILED);
    if (NULL == mini->refresh_task)
      mini->refresh_task 
        = GNUNET_SCHEDULER_add_delayed (MAP_REFRESH_FREQ,
					&do_refresh,
					mini);
    return;
  }
  /*
   * The upnpc output we're after looks like this:
   *
   * "external 87.123.42.204:3000 TCP is redirected to internal 192.168.2.150:3000"
   */
  if ((NULL == (ipaddr = strstr (line, " "))) ||
      (NULL == (pstr = strstr (ipaddr, ":"))) ||
      (1 != SSCANF (pstr + 1, "%u", &port)))
  {
    return;                     /* skip line */
  }
  ipa = GNUNET_strdup (ipaddr + 1);
  strstr (ipa, ":")[0] = '\0';
  if (1 != inet_pton (AF_INET,
		      ipa,
		      &mini->current_addr.sin_addr))
  {
    GNUNET_free (ipa);
    return;                     /* skip line */
  }
  GNUNET_free (ipa);

  mini->current_addr.sin_port = htons (port);
  mini->current_addr.sin_family = AF_INET;
#if HAVE_SOCKADDR_IN_SIN_LEN
  mini->current_addr.sin_len = sizeof (struct sockaddr_in);
#endif
  mini->did_map = GNUNET_YES;
  mini->ac (mini->ac_cls,
	    GNUNET_YES,
            (const struct sockaddr *) &mini->current_addr,
            sizeof (mini->current_addr),
            GNUNET_NAT_ERROR_SUCCESS);
}
/**
 * Process the output from "upnpc -l" to see if our
 * external mapping changed.  If so, do the notifications.
 *
 * @param cls the `struct GNUNET_NAT_MiniHandle`
 * @param line line of output, NULL at the end
 */
static void
process_refresh_output (void *cls,
			const char *line)
{
  struct GNUNET_NAT_MiniHandle *mini = cls;
  char pstr[9];
  const char *s;
  unsigned int nport;
  struct in_addr exip;

  if (NULL == line)
  {
    GNUNET_OS_command_stop (mini->refresh_cmd);
    mini->refresh_cmd = NULL;
    if (GNUNET_NO == mini->found)
    {
      /* mapping disappeared, try to re-create */
      if (GNUNET_YES == mini->did_map)
      {
        mini->ac (mini->ac_cls,
                  GNUNET_NO,
                  (const struct sockaddr *) &mini->current_addr,
                  sizeof (mini->current_addr),
                  GNUNET_NAT_ERROR_SUCCESS);
        mini->did_map = GNUNET_NO;
      }
      run_upnpc_r (mini);
    }
    return;
  }
  if (! mini->did_map)
    return;                     /* never mapped, won't find our mapping anyway */

  /* we're looking for output of the form:
   * "ExternalIPAddress = 12.134.41.124" */

  s = strstr (line,
	      "ExternalIPAddress = ");
  if (NULL != s)
  {
    s += strlen ("ExternalIPAddress = ");
    if (1 != inet_pton (AF_INET,
			s,
			&exip))
      return;                   /* skip */
    if (exip.s_addr == mini->current_addr.sin_addr.s_addr)
      return;                   /* no change */
    /* update mapping */
    mini->ac (mini->ac_cls,
	      GNUNET_NO,
              (const struct sockaddr *) &mini->current_addr,
              sizeof (mini->current_addr),
              GNUNET_NAT_ERROR_SUCCESS);
    mini->current_addr.sin_addr = exip;
    mini->ac (mini->ac_cls,
	      GNUNET_YES,
              (const struct sockaddr *) &mini->current_addr,
              sizeof (mini->current_addr),
              GNUNET_NAT_ERROR_SUCCESS);
    return;
  }
  /*
   * we're looking for output of the form:
   *
   * "0 TCP  3000->192.168.2.150:3000  'libminiupnpc' ''"
   * "1 UDP  3001->192.168.2.150:3001  'libminiupnpc' ''"
   *
   * the pattern we look for is:
   *
   * "%s TCP  PORT->STRING:OURPORT *" or
   * "%s UDP  PORT->STRING:OURPORT *"
   */
  GNUNET_snprintf (pstr,
		   sizeof (pstr),
		   ":%u ",
		   mini->port);
  if (NULL == (s = strstr (line, "->")))
    return;                     /* skip */
  if (NULL == strstr (s, pstr))
    return;                     /* skip */
  if (1 !=
      SSCANF (line,
              (mini->is_tcp) ? "%*u TCP  %u->%*s:%*u %*s" :
              "%*u UDP  %u->%*s:%*u %*s", &nport))
    return;                     /* skip */
  mini->found = GNUNET_YES;
  if (nport == ntohs (mini->current_addr.sin_port))
    return;                     /* no change */

  /* external port changed, update mapping */
  mini->ac (mini->ac_cls,
	    GNUNET_NO,
            (const struct sockaddr *) &mini->current_addr,
            sizeof (mini->current_addr),
            GNUNET_NAT_ERROR_SUCCESS);
  mini->current_addr.sin_port = htons ((uint16_t) nport);
  mini->ac (mini->ac_cls,
	    GNUNET_YES,
            (const struct sockaddr *) &mini->current_addr,
            sizeof (mini->current_addr),
            GNUNET_NAT_ERROR_SUCCESS);
}