#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <getopt.h>
#include "dns.h"
#include "socket.h"
#include "buffer.h"
#include "ip6.h"
#include "str.h"
#include "uint16.h"
#include "str.h"
#include "iopause.h"
#include "fmt.h"
#include "byte.h"
#include "ndelay.h"
#include "errmsg.h"
#include <sys/poll.h>
#include <string.h>

static char seed[128];

char mc6ip[16]={0xff,2,0,0,0,0,0,0,0,0,0,0,'n','c','p','0'};
char mc4ip[4]={224,'n','c','p'};
char bcip[4]={0xff,0xff,0xff,0xff};

static char message[]="ncp-lowfat-1.2.2";

#if 0
long int socket_sendfile(int destfd,int sourcefd,long int maxcopy) {
  char buf[16*1024];
  int len,written;
  long int done=0;
  while (maxcopy-done>0) {
    if (maxcopy-done>16*1024)
      len=read(sourcefd,buf,16*1024);
    else
      len=read(sourcefd,buf,maxcopy-done);
    len=read(sourcefd,buf,16*1024);
    if (len<0) return -1;
    if (len==0) return done;
    written=write(destfd,buf,len);
    if (written<0) return -1;
    if (written<len) return done+written;
    done+=written;
  }
  return done;
}
#endif

int v6mcsock() {
  int fd;
  if ((fd=socket_udp6())!=-1) {
    if (socket_bind6_reuse(fd,(char *)V6any,8002,0)!=-1) {
      socket_mcloop6(fd,1);
      return fd;
    }
    close(fd);
  }
  return -1;
}

int v4mcsock() {
  int fd;
  if ((fd=socket_udp4())!=-1) {
    if (socket_bind4_reuse(fd,(char *)V6any,8002)!=-1) {
      socket_mcloop4(fd,1);
      return fd;
    }
    close(fd);
  }
  return -1;
}

int v4bcsock() {
  int fd;
  if ((fd=socket_udp4())!=-1) {
    if (socket_bind4_reuse(fd,(char *)V6any,8002)!=-1)
     if (socket_broadcast(fd)!=-1)
	return fd;
    close(fd);
  }
  return -1;
}

int v6tcpsock() {
  int s;
  if ((s=socket_tcp6())!=-1) {
    if (socket_bind6_reuse(s,(char *)V6any,8002,0)!=-1)
      if (socket_listen(s,1)!=-1)
	return s;
    close(s);
  }
  return -1;
}

int v4tcpsock() {
  int s;
  if ((s=socket_tcp4())!=-1) {
    if (socket_bind4_reuse(s,(char *)V6any,8002)!=-1)
      if (socket_listen(s,1)!=-1)
	return s;
    close(s);
  }
  return -1;
}

void npush(int argc,char *argv[]) {
  int fd;
  int s,s4;
  char **newargv;
  int i;
  int fds[4];
  int forcebc=0;
  int whined[3];
  int fl=0;
  struct pollfd pfd[2];

  if (*argv) {
    if (str_equal(*argv,"--usage") || str_equal(*argv,"--help"))
      die(0,"usage: npush [filename...]\nWill send stdin when no filenames are given");
    if (str_equal(*argv,"-b")) {
      forcebc=1; ++argv; --argc;
    }
  }
  fds[0]=fds[1]=fds[2]=-1;

  if ((fds[0]=v6mcsock())==-1)
    carpsys("could not create IPv6 multicast UDP socket");
  if ((fds[1]=v4mcsock())==-1)
    carpsys("could not create IPv4 multicast UDP socket");

  if (forcebc || (fds[0]==-1 && fds[1]==-1))
    if ((fds[2]=v4bcsock())==-1)
      carpsys("could not create IPv4 broadcast UDP socket");

  if ((s=v6tcpsock())==-1)
    carpsys("could not create IPv6 TCP socket");
  if ((s4=v4tcpsock())==-1)
    if (errno!=EADDRINUSE)
      carpsys("could not create IPv4 TCP socket");

  if (fds[0]==-1 && fds[1]==-1 && fds[2]==-1)
    die(111,"can not continue without at least one UDP socket");

  if (s==-1 && s4==-1)
    die(111,"can not continue without at least one TCP socket");

  whined[0]=whined[1]=whined[2]=0;
  if (s!=-1) {
    pfd[0].fd=s;
    pfd[0].events=POLLIN;
    ++fl;
  }
  if (s4!=-1) {
    pfd[fl].fd=s4;
    pfd[fl].events=POLLIN;
    ++fl;
  }

  for (;;) {
    int i;
    if (fds[0]!=-1)
      if (socket_send6(fds[0],message,str_len(message),mc6ip,8002,0)==-1) {
	if (!whined[0]) carpsys("IPv6 multicast sendmsg failed");
	whined[0]=1;
      }
    if (fds[1]!=-1)
      if (socket_send4(fds[1],message,str_len(message),mc4ip,8002)==-1) {
	if (!whined[1]) carpsys("IPv4 multicast sendmsg failed");
	whined[1]=1;
      }
    if (fds[2]!=-1)
      if (socket_send4(fds[2],message,str_len(message),bcip,8002)==-1) {
	if (!whined[2]) carpsys("IPv4 broadcast sendmsg failed");
	whined[2]=1;
      }
    if ((i=poll(pfd,fl,200))==-1)
      die(111,"poll");
    if (i>0) {
      if (s!=-1 && (pfd[0].revents&POLLIN)) {
	fd=socket_accept6(s,0,0,0);
	if (fd==-1) carp("accept"); else break;
      }
      if (s4!=-1 && (pfd[fl-1].revents&POLLIN)) {
	fd=socket_accept4(s4,0,0);
	if (fd==-1) carp("accept"); else break;
      }
    }
  }
  if (fds[0]!=-1) close(fds[0]);
  if (fds[1]!=-1) close(fds[1]);
  if (fds[2]!=-1) close(fds[2]);
  ndelay_off(fd);
  close(s);
  if (!*argv) {	/* if command line is empty, send \0 and stdin */
    char c=0;
    char buf[1500];
    int len,written;
    if (write(fd,&c,1)!=1)
      diesys(111,"write failed");
    do {
      len=read(0,buf,1500);
      if (len==-1)
	diesys(111,"read failed");
      if (len==0)
	break;
      written=write(fd,buf,len);
      if (written==-1)
	diesys(111,"write failed");
      if (written<len)
	diesys(111,"short write");
    } while (1);
    close(fd);
    exit(0);
  }
  close(1);
  close(0);
  if (dup2(fd,1) == -1 || close(fd)==-1)
    diesys(111,"unable to set up stdout");
  newargv=(char **)alloca((argc+2)*sizeof(char*));
  newargv[0]="tar";
  newargv[1]="cpvvf";
  newargv[2]="-";
  i=3;
  while (*argv)
    newargv[i++]=*argv++;
  newargv[i]=0;
  execvp(newargv[0],newargv);
  diesys(111,"unable to run tar");
  exit(0);
}

void npoll(int argc,char *argv[]) {
  /* TODO: look for UDP packets, connect to sending IP.
    * read 1 byte.  If it is \0, dump the rest to stdout.
    * Otherwise, create a pipe to tar x, write the byte followed by the
    * socket data to the pipe. */
  char buf[1600];
  char ip[16];
  char portstr[10];
  uint16 port;
  char **newargv;
  int pipefd[2];
  int s;
  pid_t pid;
  stralloc out={0};
  int outidx=0;
  uint32 scope_id=0;

  if (*argv && (str_equal(*argv,"--usage") || str_equal(*argv,"--help")))
    die(0,"usage: npoll [hostname...]\nWith hostname, will retrieve files/stdin from hostname.\n"
		 "Without hostname, will look for announcement packets from npush to find sender.");
  if (*argv) {
    stralloc fqdn={0};
    stralloc tmp={0};

    if (!stralloc_copys(&tmp,*argv))
      die(111,"out of memory");
    if (dns_ip6_qualify(&out,&fqdn,&tmp) == -1)
      die(111,"temporary unable to figure out IP address for ",*argv);
    if (out.len < 16)
      die(111,"no IP address for ",*argv);
    byte_copy(ip,16,out.s);
    buf[fmt_ip6(buf,ip)]=0;
    carp("connecting to ",buf);
  } else {
    if ((s=socket_udp6())==-1)
      diesys(111,"could not create socket");
    if (socket_bind6_reuse(s,(char *)V6any,8002,0)==-1)
      diesys(111,"could not bind");
    socket_mcjoin6(s,mc6ip,0);
    socket_mcjoin4(s,mc4ip,(char *)V6any);
    ndelay_off(s);
    for (;;) {
      if (socket_recv6(s,buf,1550,ip,&port,&scope_id)==-1)
	diesys(111,"could not receive UDP packet");
      if (port == 8002)
	break;
    }
    buf[fmt_ip6(buf,ip)]=0;
    portstr[fmt_ulong(portstr,port)]=0;
    if (scope_id)
      carp("got packet from ",buf," port ",portstr," on interface ",socket_getifname(scope_id));
    else
      carp("got packet from ",buf," port ",portstr);
    close(s);
  }

  for (;;) {
    s = socket_tcp6();
    if (s==-1)
      diesys(111,"socket");
    if (socket_bind6(s,(char *)V6any,0,0) == -1)
      diesys(111,"bind");
    ndelay_off(s);
    if (socket_connect6(s,ip,8002,scope_id) == -1) {
/*    if (timeoutconn6(s,ip,8002,scope_id,60) == -1) { */
      if (out.len>outidx+16) {
	outidx+=16;
	byte_copy(ip,16,out.s+outidx);
	buf[fmt_ip6(buf,ip)]=0;
	carp("connecting to ",buf);
	close(s);
	continue;
      }
      diesys(111,"connection to ",*argv," failed");
    } else
      break;
  }
  ndelay_off(s);
  if (read(s,buf,1)!=1)
    diesys(111,"read");
  if (buf[0]==0) {	/* stdin/stdout mode */
    int len,written;
    do {
      len=read(s,buf,1500);
      if (len==-1)
	diesys(111,"read");
      if (len==0)
	break;
      written=write(1,buf,len);
      if (written==-1)
	diesys(111,"write");
      if (written<len)
	die(111,"short write");
    } while (1);
    close(s);
    exit(0);
  }
  /* unfortunately, since we read the first byte, we can't just exec
   * tar now, we need to create a pipe, fork, exec tar and copy the
   * bytes. */
  if (pipe(pipefd)==-1)
    diesys(111,"pipe failed");
  switch (pid=fork()) {
  case 0:
    close(pipefd[1]);
    close(0);
    if (dup2(pipefd[0],0)==-1 || close(pipefd[0])==-1)
      diesys(111,"unable to set up stdin");
    newargv=(char **)alloca(4*sizeof(char *));
#if 1
    newargv[0]="tar";
    newargv[1]="xvvpf";
    newargv[2]="-";
    newargv[3]=0;
#else
    newargv[0]="strace";
    newargv[1]="tar";
    newargv[2]="xvvpf";
    newargv[3]="-";
    newargv[4]=0;
#endif
    execvp(newargv[0],newargv);
    diesys(111,"unable to run tar");

  case -1:
    diesys(111,"fork");
  }
  close(pipefd[0]);
  ndelay_off(1);
  if (write(pipefd[1],buf,1)==-1)
    diesys(111,"write");
  {
    int len,written;
    do {
      len=read(s,buf,1500);
      if (len==-1)
	diesys(111,"read");
      if (len==0)
	break;
      written=write(pipefd[1],buf,len);
      if (written==-1)
	diesys(111,"write");
      if (written<len)
	die(111,"short write");
    } while (1);
  }
  close(s);
  close(pipefd[1]);
  waitpid(pid,0,0);
  exit(0);
}

void ncpserver(int argc,char *argv[]) {
  char remoteip[16];
  int fd,s;
  uint16 remoteport;
  char **newargv;

  carp("server mode.  waiting for connection.");
  s = socket_tcp6();
  if (s == -1)
    diesys(111,"socket");
  if (socket_bind6_reuse(s,(char *)V6any,8002,0) == -1)
    diesys(111,"bind");
  if (socket_listen(s,1) == -1)
    diesys(111,"listen");
  ndelay_off(s);
  if ((fd=socket_accept6(s,remoteip,&remoteport,0)) == -1)
    diesys(111,"accept");
  close(0);
  if (dup2(fd,0)==-1 || close(fd)==-1)
    diesys(111,"dup2");
  newargv=(char **)alloca(4*sizeof(char *));
  newargv[0]="tar";
  newargv[1]="xvvpf";
  newargv[2]="-";
  newargv[3]=0;
  execvp(newargv[0],newargv);
  diesys(111,"unable to run tar");
}

void ncpclient(int argc,char *argv[]) {
  int s,i;
  char **newargv;
  stralloc fqdn={0};
  stralloc out={0};
  stralloc tmp={0};

  if (str_equal(*argv,"--usage") || str_equal(*argv,"--help"))
    die(0,"usage: server mode: ncp hostname filename [filename...]\n"
		   "       client mode: ncp");
  if (!stralloc_copys(&tmp,*argv))
    die(111,"out of memory");
  if (dns_ip6_qualify(&out,&fqdn,&tmp) == -1)
    diesys(111,"temporary unable to figure out IP address for ",*argv);
  if (out.len < 16)
    die(111,"no IP address for ",*argv);
  s = socket_tcp6();
  if (s==-1)
    diesys(111,"socket");
  if (socket_bind6(s,(char *)V6any,0,0) == -1)
    diesys(111,"bind");
  if (socket_connect6(s,out.s,8002,0) == -1)
    diesys(111,"connection to ",*argv);
  ndelay_off(s);
  close(1);
  if (dup2(s,1)==-1 || close(s))
    diesys(111,"dup2");
  newargv=(char **)alloca((argc+2)*sizeof(char *));
  newargv[0]="tar";
  newargv[1]="cpvvf";
  newargv[2]="-";
  i=3;
  argv++;
  while (*argv)
    newargv[i++]=*argv++;
  newargv[i]=0;
  execvp(newargv[0],newargv);
  diesys(111,"unable to run tar");
}

int main(int argc,char *argv[]) {
  char *argv0=*argv;

  dns_random_init(seed);
  if (!argv0) die(111,"no argv[0]?!");
  argv0+=str_rchr(argv0,'/');
  if (*argv0=='/') argv0++; else argv0=*argv;
  errmsg_iam(argv0);
  argv++;
  if (*argv && str_equal(*argv,"--version"))
    die(0,message," (libowfat)\nWritten by Felix von Leitner <felix-ncp@fefe.de>\n"
		  "This is free software; copy and use it as you like.  No warranty.\n");
  if (str_equal(argv0,"npush"))
    npush(argc,argv);
  else if (str_equal(argv0,"npoll"))
    npoll(argc,argv);
  else if (!*argv)
    ncpserver(argc,argv);
  else
    ncpclient(argc,argv);
  return 0;
}



syntax highlighted by Code2HTML, v. 0.9.1