/*
** dcc.c (for irchat-jp)
** Copyright (C) 1995,1996,1998,1999 KIKUCHI Takahiro
**
** Author:        KIKUCHI Takahiro <kick@kyoto.wide.ad.jp>
** Created:       Mar 19, 1995
** Last Modified: Jul  7, 1999
*/

/*
** dcc.c for irchat-pj by irchat-PJ Project
** - modified for Linux GLIBC by simm@irc.fan.gr.jp
** - modified for Microsoft Windows by quiver@tky3.3web.ne.jp
** - modified for OS/2 Warp by yuu@cb3.so-net.ne.jp
** - merge by simm@irc.fan.gr.jp, Sun, 20 Feb 2000 03:50:01 +0900
**
** $Id: dcc.c,v 1.5 2000/11/05 15:01:53 simm Exp $
*/

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <netdb.h>
#ifdef	AIX
# include <sys/select.h>
#endif
#ifdef	linux
# ifndef __GLIBC__
#  include <linux/time.h>
# endif
#endif
#ifdef	SOCKS
# include <socks.h>
#endif

#ifndef O_BINARY
#define O_BINARY (0)
#endif

#ifdef USE_PTHREAD
# define PTHREAD_KERNEL
# include <pthread.h>
# include <signal.h>
# include <errno.h>
  void *wait_resume();
#endif

#define DCC_NORESUME 0
#define DCC_RESUME   1


long position = 0;

int main(argc, argv)
int argc;
char **argv;
{
    char *command, *type, *action;
    int status = 1;
    extern u_long atoul();
    
#ifdef __EMX__             /*  for emx OS/2    */
   _fsetmode(stdout,"b");  /* stdout is binary */
   _fsetmode(stderr,"b");  /* stderr is binary */
#endif

    command = *argv++, argc--;
    if (argc < 2) {
	printf("DCC ERROR Not enough parameters\n");
	fprintf(stderr, "Usage: %s <type> <command> <arg>...\n", command);
	exit(1);
    }

    type = *argv++, argc--;
    action = *argv++, argc--;

    if (!stricmp(type, "check")) {
#ifdef USE_PTHREAD
	printf("DCC RESUME OK\n");
	return 0;
#else
	printf("DCC RESUME NG\n");
	return 1;
#endif	
    } else if (!stricmp(type, "file")) {
	if (!stricmp(action, "send")) {
	    if (argc == 1) {
		status = file_send(argv[0], 0, 0);
	    } else if (argc == 2) {
		status = file_send(argv[0], atoi(argv[1]), 0);
	    } else if (argc == 3) {
		status = file_send(argv[0], atoi(argv[1]), argv[2]);
	    } else {
		printf("DCC ERROR Wrong number of parameters\n");
		fprintf(stderr,
			"Usage: %s file send <filename> <port> <ircserver>\n",
			command);
		status = 1;
	    }
	} else if (!stricmp(action, "get")) {
	    if (argc == 4 || argc == 5) {
		status = file_get(atoul(argv[0]), atoi(argv[1]),
				  atoi(argv[2]), argv[3],"",DCC_NORESUME);
	    } else {
		printf("DCC ERROR Wrong number of parameters\n");
		fprintf(stderr,
			"Usage: %s file get <addr> <port> <size> <filename>\n",
			command);
		status = 1;
	    }
	} else if (!stricmp(action, "rget")) {
	  if (argc == 5) {
	    status = file_get(atoul(argv[0]),atoi(argv[1]),
			      atoi(argv[2]),argv[3],argv[4],DCC_RESUME);
	  } else if (argc == 4) {
	    status = file_get(atoul(argv[0]),atoi(argv[1]),
			      atoi(argv[2]),argv[3],argv[3],DCC_RESUME);
	  } else {
		printf("DCC ERROR Wrong number of parameters\n");
		fprintf(stderr,
			"Usage: %s file rget <addr> <port> <size> <filename> <filename2> \n",
			command);
		status = 1;
	  }
	} else {
	    printf("DCC ERROR Unsupported command %s %s\n", type, action);
	    status = 1;
	}
    } else if (!stricmp(type, "chat")) {
	if (!stricmp(action, "listen")) {
	    if (argc == 0) {
		status = chat_listen(0, 0);
	    } else if (argc == 1) {
		status = chat_listen(atoi(argv[0]), 0);
	    } else if (argc == 2) {
		status = chat_listen(atoi(argv[0]), argv[1]);
	    } else {
		printf("DCC ERROR Wrong number of parameters\n");
		fprintf(stderr, "Usage: %s chat listen <port> <ircserver>\n",
			command);
		status = 1;
	    }
	} else if (!stricmp(action, "connect")) {
	    if (argc == 2) {
		status = chat_connect(atoul(argv[0]), atoi(argv[1]));
	    } else {
		printf("DCC ERROR Wrong number of parameters\n");
		fprintf(stderr, "Usage: %s chat connect <addr> <port>\n",
			command);
		status = 1;
	    }
	} else {
	    printf("DCC ERROR Unsupported command %s %s\n", type, action);
	    status = 1;
	}
    } else if (!stricmp(type, "tcp")) {
	if (!stricmp(action, "connect")) {
	    if (argc == 2) {
		status = tcp_connect(argv[0], argv[1]);
	    } else {
		printf("DCC ERROR Wrong number of parameters\n");
		fprintf(stderr, "Usage: %s chat connect <addr> <port>\n",
			command);
		status = 1;
	    }
	} else {
	    printf("DCC ERROR Unsupported command %s %s\n", type, action);
	    status = 1;
	}
    } else {
	printf("DCC ERROR Unsupported command %s\n", type);
	status = 1;
    }
    if (status) {
	sleep(3);
    }
    exit(status);
}

#ifdef _WIN32
u_long getmyaddr(ircserver)
char *ircserver;
{
        int i;
        struct hostent *hp;
        char hostname[50];
        struct in_addr addr;

        gethostname(hostname, sizeof(hostname));
        hp = gethostbyname(hostname);
        for (i = 0; hp->h_addr_list[i] != 0; ++i);
        memcpy(&addr, hp->h_addr_list[i-1], sizeof(struct in_addr));
    return ntohl(addr.s_addr);
}
#else
u_long getmyaddr(ircserver)
char *ircserver;
{
    int i, len, dummy;
    u_long addr;
    char *p;
    struct hostent *hp;
    struct sockaddr_in server, client;

    addr = 0xc6290004;				/* dummy addr --- rootA */
    if (ircserver && (hp = gethostbyname(ircserver)) != NULL) {
	addr = ntohl(((struct in_addr *)hp->h_addr_list[0])->s_addr);
    }
    if ((dummy = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
	printf("DCC ERROR Cannot create socket\n");
	return -1;
    }
    for (i = 0, p = (char *)&server; i < sizeof(server); i++, *p++ = 0);
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = htonl(addr);
    server.sin_port = htons(7);			/* dummy port --- echo */
    if (connect(dummy, (struct sockaddr *)&server, sizeof(server)) < 0) {
	printf("DCC ERROR Cannot connect socket\n");
	return -1;
    }
    len = sizeof(client);
    if (getsockname(dummy, (struct sockaddr *)&client, &len) < 0) {
	printf("DCC ERROR Cannot getsockname\n");
	return -1;
    }
    close(dummy);
    return ntohl(client.sin_addr.s_addr);
}
#endif

int dcc_listen(paddr, pport, ircserver)
u_long *paddr;
u_short *pport;
char *ircserver;
{
    int i, s, len;
    char *p;
    struct sockaddr_in server;

    if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
	printf("DCC ERROR Cannot create socket\n");
	return -1;
    }
    for (i = 0, p = (char *)&server; i < sizeof(server); i++, *p++ = 0);
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = htonl(getmyaddr(ircserver));
    server.sin_port = htons(*pport);
    while (bind(s, (struct sockaddr *)&server, sizeof(server)) < 0) {
	if (!*pport) {
	    printf("DCC ERROR Cannot bind socket\n");
	    return -1;
	}
	server.sin_port = htons(++(*pport));
    }
    if (listen(s, 1) < 0) {
	printf("DCC ERROR Cannot listen socket\n");
	return -1;
    }
    len = sizeof(server);
    if (getsockname(s, (struct sockaddr *)&server, &len) < 0) {
	printf("DCC ERROR Cannot getsockname\n");
	return -1;
    }	
    *pport = ntohs(server.sin_port);
    *paddr = ntohl(server.sin_addr.s_addr);

    return s;
}

int dcc_connect(addr, port)
u_long addr;
u_short port;
{
    int i, remote;
    char *p;
    struct sockaddr_in server;

    if ((remote = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
	printf("DCC ERROR Cannot create socket\n");
	return -1;
    }
    for (i = 0, p = (char *)&server; i < sizeof(server); i++, *p++ = 0);
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = htonl(addr);
    server.sin_port = htons(port);
    if (connect(remote, (struct sockaddr *)&server, sizeof(server)) < 0) {
	printf("DCC ERROR Cannot connect %u/%d\n", addr, port);
	return -1;
    }
    return remote;
}

u_long atoul(str)
char *str;
{
    u_long val = 0;

    while (*str) {
	val = val * 10 + *str - '0';
	str++;
    }
    return val;
}

u_long dottoul(str)
char *str;
{
    u_long val, v;

    for (val = v = 0; *str; str++) {
	if (*str == '.') {
	    val = val * 256 + v;
	    v = 0;
	} else {
	    v = v * 10 + *str - '0';
	}
    }
    val = val * 256 + v;
    return val;
}

int file_send(filename, port, ircserver)
char *filename;
u_short port;
char *ircserver;
{
    int i, file, size, s, remote, len, count = 0;
    u_long addr;
    struct stat statbuf;
    u_long done = 0;
    u_long report;
    char buf[4096];

#ifdef USE_PTHREAD
    pthread_t thread;
# ifdef ENABLE_PTHREAD_ATTR
    pthread_attr_t th_attr;
# endif
#endif

    if ((file = open(filename, O_RDONLY|O_BINARY)) < 0) {
	printf("DCC ERROR Cannot open file %s\n", filename);
	return 1;
    }
    if (fstat(file, &statbuf) < 0) {
	printf("DCC ERROR Cannot stat file %s\n", filename);
	return 1;
    }
    size = statbuf.st_size;

    if ((s = dcc_listen(&addr, &port, ircserver)) < 0) {
	/* error report in dcc_listen */
	return 1;
    }
    /* 'Setting -> 'Waiting (send DCC SEND to remote user) */
    printf("DCC SEND %u %d %d\n", addr, port, size);

#ifdef USE_PTHREAD   
    /* Create Thread wait_resume() : Waiting DCC Resume    */
    position = 0;
# ifdef ENABLE_PTHREAD_ATTR
    if(pthread_attr_init(&th_attr));
    pthread_attr_setdetachstate(&th_attr,PTHREAD_CREATE_DETACHED);
    if(pthread_create(&thread,&th_attr,wait_resume,NULL))
# else
    if(pthread_create(&thread,NULL,wait_resume,NULL))
# endif
      printf("DCC WARNING Not Support Send Resume\n");
#endif

    remote = accept(s, (struct sockaddr *) 0, (int *) 0);
    close(s);
    /* 'Waiting -> 'Sending (accepted from remote user) */
    printf("DCC SENDING\n");

#ifdef USE_PTHREAD
    /* Interrupt Thread : wait_resume() */
# ifndef USE_PTHREAD_KILL
    pthread_cancel(thread);
# else
    if(pthread_kill(thread,0) != ESRCH) pthread_kill(thread,SIGINT);
# endif /* USE_PTHREAD_KILL */
# ifdef ENABLE_PTHREAD_ATTR
    pthread_attr_destroy(&th_attr);
# endif /* ENABLE_PTHREAD_ATTR*/
    /* Resume Point position by wait_resume() */
    if(done != position) done = lseek(file,position,SEEK_SET);
#endif /* USE_PTHREAD */

    while ((len = read(file, buf, sizeof(buf))) > 0) {
	write(remote, buf, len);
	done += len;
	if (++count == 16) {
	    count = 0;
	    while (read(remote, &report, sizeof(u_long)) > 0 &&
		   ntohl(report) != done);
	    printf("DCC REPORT %s %d%% (%d/%d bytes) sent\n",
		   filename, 100 * done / size, done, size);
	}
    }
    while (read(remote, &report, sizeof(u_long)) > 0 &&
	   ntohl(report) != done);

    /* 'Sending -> end */
    close(remote);
    close(file);
    return 0;
}

int file_get(addr, port, size, filename,filename2,resume)
u_long addr;
u_short port;
int size;
char *filename;
char *filename2;
u_short resume;
{
    int i, remote, file, len, oflag, toread, count = 0;
    u_short rport;
    u_long done = 0;
    u_long report;
    char dcc[32],cmd[32],fn[256],buf[4096];
    
    oflag = O_WRONLY|O_BINARY|O_CREAT;
    if(resume == DCC_NORESUME) oflag = oflag | O_TRUNC;
    rport = port;

    if ((file = open(filename,oflag, 0600)) < 0) {
	printf("DCC ERROR1 Cannot open file %s\n", filename);
	return 1;
    }
    if(resume == DCC_RESUME) {
      position = 0;
      done = lseek(file,0,SEEK_END);
      printf("DCC RESUME %s %d %d\n",filename2,port,done);
      fgets(buf,sizeof(buf),stdin);
      if((sscanf(buf,"%s %s %s %d %d",dcc,cmd,fn,&rport,&position) != 5)
	 || stricmp(dcc,"DCC") || stricmp(cmd,"ACCEPT")){
	printf("%s,%s,%s,%d,%d\n",dcc,cmd,fn,port,position);
	printf("DCC ERROR Unsupported command %s\n",buf);
	return 1;
      }
      if(done != position) done = lseek(file,position,SEEK_SET);
    }


    if ((remote = dcc_connect(addr, rport)) < 0) {
	/* error report in dcc_connect */
	return 1;
    }
    /* 'Connect -> 'Getting (connected to remote user) */
    printf("DCC GETTING\n");

    toread = sizeof(buf);
    while (size - done > 0) {
	if (toread > size - done) {
	    toread = size - done;
	}
	if ((len = read(remote, buf, toread)) < 0) {
	    printf("DCC ERROR read error %s\n", filename);
	    return 1;
	}
	write(file, buf, len);
	done += len;
	report = htonl(done);
	write(remote, &report, sizeof(report));
	if (++count == 16) {
	    count = 0;
	    printf("DCC REPORT %s %d%% (%d/%d bytes) received\n",
		   filename, 100 * done / size, done, size);
	}
    }

    /* 'Getting -> end */
    close(remote);
    close(file);
    return 0;
}

int chat_listen(port, ircserver)
u_short port;
char *ircserver;
{
    u_long addr;
    int s, remote;

    if ((s = dcc_listen(&addr, &port, ircserver)) < 0) {
	return 1;
    }
    /* 'Setting -> 'Waiting (send DCC CHAT to remote user) */
    printf("DCC CHAT %u %d\n", addr, port);

    remote = accept(s, (struct sockaddr *) 0, (int *) 0);
    close(s);
    /* 'Waiting -> 'Active (accepted from remote user) */
    printf("DCC CHATTING\n");
    return loop(remote);
}

int chat_connect(addr, port)
u_long addr;
u_short port;
{
    int remote;

    if ((remote = dcc_connect(addr, port)) < 0) {
	return 1;
    }
    /* 'Connect -> 'Active (connected to remote user) */
    printf("DCC CHATTING\n");
    return loop(remote);
}

int loop(remote)
int remote;
{
    int n, len, cnt;
    char *ptr, buf[1024];
    fd_set rfds;

    while (1) {
	FD_ZERO(&rfds);
	FD_SET(0, &rfds);
	FD_SET(remote, &rfds);
	if ((n = select(FD_SETSIZE, &rfds, NULL, NULL, NULL)) == -1) {
	    continue;
	}
	if (FD_ISSET(0, &rfds)) {
	    if ((len = read(0, buf, sizeof(buf))) <= 0) {
		close(0);
		close(remote);
		if (len == 0) {
		    return 0;
		} else {
		    return 1;
		}
	    }
	    for (ptr = buf; len > 0; ptr+= cnt, len -= cnt) {
		if ((cnt = write(remote, ptr, len)) < 0) {
		    close(0);
		    close(remote);
		    return 1;
		}
	    }
	}
	if (FD_ISSET(remote, &rfds)) {
	    if ((len = read(remote, buf, sizeof(buf))) <= 0) {
		close(0);
		close(remote);
		if (len == 0) {
		    return 0;
		} else {
		    return 1;
		}
	    }
	    for (ptr = buf; len > 0; ptr+= cnt, len -= cnt) {
		if ((cnt = write(0, ptr, len)) < 0) {
		    close(0);
		    close(remote);
		    return 1;
		}
	    }
	}
    }
}

int tcp_connect(straddr, strport)
char *straddr, *strport;
{
    int remote;
  
#ifdef INET6
    int eai;
    struct addrinfo hints, *ai;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    if ((eai = getaddrinfo(straddr, strport, &hints, &ai)) != 0) {
        printf("ERROR :Closing Link: [*@*] (Cannot resolve %s: %s)\n",
	       straddr, gai_strerror(eai));
        return 0;
    }
    if ((remote = socket(ai->ai_family, SOCK_STREAM, 0)) < 0) {
        printf("ERROR :Closing Link: [*@*] (Cannot create socket)\n");
	return 0;
    }
    if (connect(remote, ai->ai_addr, ai->ai_addrlen) < 0) {
        printf("ERROR :Closing Link: [*@*] (Cannot connect to %s/%s)\n",
	       straddr, strport);
	return 0;
    }
#else
    int i;
    u_long addr;
    char *p;
    struct hostent *hp;
    struct sockaddr_in server;

    if (*straddr >= '0' && *straddr <= '9') {
        addr = dottoul(straddr);
    } else if ((hp = gethostbyname(straddr)) != NULL) {
	addr = ntohl(((struct in_addr *)hp->h_addr_list[0])->s_addr);
    } else {
        printf("ERROR :Closing Link: [*@*] (Cannot resolve %s)\n", straddr);
	return 0;
    }
    if ((remote = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("ERROR :Closing Link: [*@*] (Cannot create socket)\n");
	return 0;
    }
    for (i = 0, p = (char *)&server; i < sizeof(server); i++, *p++ = 0);
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = htonl(addr);
    server.sin_port = htons(atoi(strport));
    if (connect(remote, (struct sockaddr *)&server, sizeof(server)) < 0) {
        printf("ERROR :Closing Link: [*@*] (Cannot connect to %s/%s)\n",
	       straddr, strport);
	return 0;
    }
#endif

    return loop(remote);
}

#ifdef USE_PTHREAD
void *wait_resume()
{
  char dcc[32],cmd[32],fn[256],buf[1024];
  u_short port;
  long    tposition;
  fgets(buf,sizeof(buf),stdin);
  if((sscanf(buf,"%s %s %s %d %d",dcc,cmd,fn,&port,&tposition) != 5)
     || stricmp(dcc,"DCC") || stricmp(cmd,"RESUME") || (strlen(fn) == 0)){
    printf("DCC ERROR Unsupported command %s\n",buf);
    exit(1);
  }
  position = tposition;
  printf("DCC ACCEPT %s %d %d\n",fn,port,position);
  pthread_exit(0);
}
#endif

int stricmp (const char *string1, const char *string2)
{
  int d;

  for (;;)
    {
      d = tolower ((unsigned char)*string1)
        - tolower ((unsigned char)*string2);
      if (d != 0 || *string1 == 0 || *string2 == 0)
        return d;
      ++string1; ++string2;
    }
}


syntax highlighted by Code2HTML, v. 0.9.1