/*
 * File:	net.c
 *
 * Author:	Ulli Horlacher (framstag@rus.uni-stuttgart.de)
 * 
 * Contrib.:	Heiko Schlichting (heiko@fu-berlin.de)
 * 		Holger Berger (Holger.Berger@rus.uni-stuttgart.de)
 *		Andreas "Ako" Koppenhöfer (koppenhoefer@belwue.de)
 *
 * History:
 *
 *   11 Aug 95   Framstag	initial version
 *   10 Sep 95   Framstag	some debugging
 *   15 Nov 95   Framstag	improved sock_getline
 *   21 Dec 95   Framstag	simplified sock_getline and getreply
 *   17 Apr 96   Framstag	new error handling in open_connection
 *   14 May 96   Framstag	included and modified send_data()
 *   21 May 96   Framstag	gettimeofday() fix for Solaris-2
 *   20 Jun 96   Framstag	always use gethostbyname()
 *    3 Aug 96   Framstag	corrected thruput value
 *    4 Sep 96   Heiko		some fixes for IRIX
 *   24 Sep 96   Heiko		added get_domainname()
 *   19 Nov 96   Framstag	fix for broken AIX include-files
 *   29 Dec 96   Framstag	moved get_domainname to reply.c
 *   19 Jan 97   Framstag	added "resuming at ..." output
 *   25 Jan 97   Framstag	send_data can now read from stdin, too
 *   31 Jan 97   Framstag	print final transfer statistic with quiet mode 1
 *   24 Feb 97   Framstag	sprintf() -> snprintf()
 *   15 Jun 97   Framstag	added file reading test mode
 * 				corrected transfer statistics output
 *   16 Jun 97   Hobel		socket tuning
 *   30 Jun 97   Framstag	fixed bug in transfer statistics output
 *   21 Aug 97   Framstag	sendheader can handle better reply code 202
 *    9 Dec 97   Framstag	added sendcommand()
 *   15 Dec 97   Framstag	new parameter for send_data(): speed
 *   27 Feb 98	 Framstag	more verbose transfer statistics
 *    7 Mar 98	 Framstag	better detection of non-interactive mode
 * 				added -i option for more transfer information
 *    2 Apr 98	 Framstag	don't ask for server-reply on QUIT command
 *   23 Jun 98	 Framstag	fixed byte counting bug in transfer statistics
 *    4 Jul 98	 Ako		set tcp timeout option
 *    7 Jul 98	 Ako		fixed fflush(NULL) bug for SunOS 4
 *   21 Aug 98	 Framstag	added maximum thruput option for send_data
 *
 * Network routines for the the sendfile client of the sendfile package.
 * Look at net.h for a list of the functions.
 *
 * Copyright © 1995-1998 Ulli Horlacher
 * This file is covered by the GNU General Public License
 */

/*
#ifdef NEXT
  typedef unsigned char	 u_char;
  typedef unsigned short u_short;
  typedef unsigned int	 u_int;
  typedef unsigned long	 u_long;
  typedef long	daddr_t;
  typedef char *caddr_t;
  typedef long  time_t;
  #define _TIME_T
#endif
*/

#include "config.h"	/* various definitions */

#include <stdio.h>
#include <errno.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "string.h"	/* extended string functions */
#include "message.h"	/* information, warning and error messages */
#include "net.h"	/* network stuff */
#include "io.h"		/* socket read/write */


/* stupid AIX comes with no include files for networking and other stuff */
#if defined(AIX3) || defined(ULTRIX)
  #include "bsd.h"
#endif

/*
#ifdef SOLARIS2
  #ifdef _SVID_GETTOD
    int gettimeofday(struct timeval *);
  #else
    int gettimeofday(struct timeval *, void *);
  #endif
#endif
*/

#if defined(SOLARIS2)
  #ifndef fileno
    int fileno(FILE *);
  #endif
#endif

/*
#ifdef IRIX
  u_short htons(u_short hostshort);
#endif
*/

extern int client;	/* client or server flag */
extern int verbose;	/* flag for verbose mode */
extern char *prg;	/* name of the game */


/*
 * open_connection - open socket and connect to client
 *
 * INPUT:  adr  - ip address or name of server to connect to
 *         port - port number to connect to
 *
 * RETURN: socket file descriptor
 *         -1 if socket creation failed
 *         -2 if connection failed
 *         -3 if unknown host
 *
 * this function is derived from example code from
 * "Unix Networking Programming" by W. R. Stevens
 */
int open_connection(char *adr, int port) {
  int sockfd,			/* socket file descriptor */
      num=1;			/* flag for numeric ip address */
  struct sockaddr_in serv_addr;	/* internet socket */
  struct in_addr hostaddr;
  struct hostent *hostp;	/* host entity */
  char *cp,			/* character pointer */
       hostip[17],		/* server host ip address */
       hostname[MAXLEN];	/* server host name */

  /* split off port from hostname */
  strcpy(hostname,adr);
  if ((cp=strchr(hostname,':'))) *cp=0;
  
  /* open socket */
  sockfd=socket(AF_INET,SOCK_STREAM,0);
  if (sockfd<0) return(-1);
#ifdef DEBUG
  message(prg,'I',"socket ok");
#endif

  /* initialisize serv_addr */
  memset((char *) &serv_addr, 0, sizeof(serv_addr));

  /* numeric oder symbolic ip address? */
  for (cp=hostname; *cp>0; cp++) {
    if (*cp>'@') {
      num=0;
      break;
    }
  }

  /* quick hack: gethostbyname() does also work with numeric addresses */
  num=0;  
  
  /* look for server host address */
  if (num) {
    hostaddr.s_addr=inet_addr(hostname);
    hostp=gethostbyaddr((char *)&hostaddr,sizeof(hostaddr),AF_INET);
  } else
    hostp=gethostbyname(hostname);
  if (hostp==NULL) return(-3);

  /* convert binary structure to ASCII hostip */
  strcpy(hostip,inet_ntoa(*(struct in_addr *) *hostp->h_addr_list));
#ifdef DEBUG
  printf("host: %s\n",hostip);
#endif

  /* fill out server address descriptor */
  serv_addr.sin_family     =AF_INET;
  serv_addr.sin_addr.s_addr=inet_addr(hostip);
  serv_addr.sin_port       =htons(port);

  /* befor connecting, let's do some tuning :-) 	hobel */
  {
    int flag; 
    flag=1;
#ifdef SO_KEEPALIVE
    if(setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, 
		  (void *)&flag, sizeof(flag))<0)
      message(prg,'W',"could not configure socket");
#endif
#ifdef TCP_NODELAY
    flag=1;
    if(setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, 
		  (void *)&flag, sizeof(flag))<0)
      message(prg,'W',"could not configure socket");
#endif
#ifdef TCP_BUFFER
    flag=TCP_BUFFER;
    if(setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, 
		  (void *)&flag, sizeof(flag))<0)
      message(prg,'W',"could not configure socket");
    flag=TCP_BUFFER;
    if(setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, 
		  (void *)&flag, sizeof(flag))<0)
      message(prg,'W',"could not configure socket");
#endif
#ifdef TCP_RFC1323
    flag=1;
    if(setsockopt(sockfd, IPPROTO_TCP,TCP_RFC1323, 
		  (void *)&flag, sizeof(flag))<0)
      message(prg,'W',"could not configure socket");
#endif
#ifdef TCP_WINSHIFT
    flag=1;    /* this gives the power of two to be shifted */
    if(setsockopt(sockfd, IPPROTO_TCP, TCP_WINSHIFT,
		  (void *)&flag, sizeof(flag))<0)
      message(prg,'W',"could not configure socket");
#endif
  }


  /* connect to server */
  if (connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0)
    return(-2);
#ifdef DEBUG
  message(prg,'I',"connect ok");
#endif

  return(sockfd);
}


/*
 * sock_getline - get a (command) line from the network socket
 *
 * INPUT:  fd     - socket file descriptor
 *         line   - empty string
 *
 * OUTPUT: line   - read string
 *
 * RETURN: number of read bytes, -1 on error
 *
 * this function is derived from example code from
 * "Unix Networking Programming" by W. R. Stevens
 */
int sock_getline(int fd, char *line) {
  int n, rc;
  unsigned char c;
  char tmp[MAXLEN];

  *line=0;

  for (n=0; n<MAXLEN-1; n++) {
    rc=read(fd,&c,1);
    if (rc==1) {
      line[n]=c;
      if (c=='\n') break;
    } else
      return(-1);
  }

  /* too much input? */
  if (n+1==MAXLEN && line[n] != '\n') {
    if (client) {
      errno=0;
      snprintf(MAXS(tmp),"network socket data overrun (read bytes: %d)",n);
      message("",'E',tmp);
      message("",'F',line);
    }
    return(-1);
  }

  /* remove trailing cr or lf */
  line[n]=0;
  if (n>0 && line[n-1]=='\r') line[--n]=0;

  /* on verbose mode show the whole line */
  if (verbose) printf("%s\n",line);

  return(n);
}


/*
 * sock_putline - send a line to the network socket
 *
 * INPUT:  fd     - socket file descriptor
 *         line   - string to send
 *
 * RETURN: number of send bytes
 */
int sock_putline(int fd, const char *line) {
  int n;		/* number of send bytes */
  char cmd[MAXLEN];	/* command line to send */

  /* prepare string */
  strcpy(cmd,line);
  strcat(cmd,"\r\n");

  /* on verbose mode show what goes up */
  if (verbose) printf("-> %s\n",line);

  /* and up and away :-) */
  if (fd)
    n=writen(fd,cmd,strlen(cmd));
  else
    /* test mode, no real sending */
    n=strlen(cmd);

  return(n);
}


/*
 * getreply - get the reply on a command from the server
 *
 * INPUT:  fd - socket file descriptor
 *
 * RETURN: the reply line string
 */
char *getreply(int fd) {
  int len;			/* reply message length */
  char msg[MAXLEN];		/* intermediate information/error message */
  static char reply[MAXLEN];	/* reply string from server */

    
  do {
   
    if (fd) {
      
      /* get the next reply line */
      len=sock_getline(fd,reply);

      /* link failure? */
      if (len<0) {
	errno=0;
	strcpy(msg,"server has closed the connection");
	if (*reply) snprintf(MAXS(msg),"%s, last data: \"%s\"",msg,reply);
	if (client) {
	  errno=0;
	  message("",'F',msg);
	}
	return("");
      }

      /* reply message too short? */
      if (len<4) {
	errno=0;
	snprintf(MAXS(msg),"corrupt reply: \"%s\"",reply);
	if (client) {
	  errno=0;
	  message(prg,'F',msg);
	}
	return("");
      }
    } else
      /* test mode without network connection */
      strcpy(reply,"222 test mode");

  } while (reply[3]=='-');

  /* quit if there was a fatal server error */
  if (reply[0]=='4') {
    errno=0;
    snprintf(MAXS(msg),"server error: %s",&reply[4]);
    if (client) {
      errno=0;
      message(prg,'F',msg);
    }
    return("");
  }

  return(reply);
}


/*
 * sendcommand - send a command line to the network socket and get the answer
 *
 * INPUT:  fd        - socket file descriptor
 *         command   - string to send
 * 
 * OUTPUT: answer    - answer string
 *
 * RETURN: answer string
 * 
 * If answer is the NULL-pointer, the answer will bis discared.
 * See also sendheader()
 * Hack: because some SAFT-servers hang on the QUIT command, we don't ask for
 * the reply in this case.
 */
char *sendcommand(int fd, const char *command, char *answer) {
  
  if (answer) *answer=0;
  
  sock_putline(fd,command);
  
  if (answer)
    strcpy(answer,getreply(fd));
  else if (!str_eq(command,"QUIT")) getreply(fd);
  
  return(answer);
}


/*
 * sendheader - send a headerline and check the reply code
 *
 * INPUT:  fd	- socket file descriptor
 *         line	- header line
 *
 * RETURN: 0 on sucess, -1 on server fatal error, 1 on header ignored
 * 
 * Return value -1 never occurs because the program will terminate before
 * See also sendcommand()
 */
int sendheader(int fd, char *line) {
  char msg[MAXLEN],		/* intermediate information/error message */
       *reply;			/* reply string from server */

  /* on test mode return */
  if (!fd) return(0);
  
  /* send the header line */
  sock_putline(fd,line);

  /* server reply ok? */
  reply=getreply(fd);
  
  if (str_beq(reply,"200")) return(0);
  if (str_beq(reply,"202")) return(1);
  
  errno=0;
  snprintf(MAXS(msg),"server error: %s",&reply[4]);
  message(prg,'F',msg);
  		    
  return(-1);
}


/*
 * send_data - send file data
 *
 * INPUT:  sockfd	- socket file descriptor
 *         size		- bytes to send
 *         file		- file to send ("" if read from stdin)
 *         tinfo	- additional transfer information
 *         iso_name	- name of the original file
 * 	   type		- file type string
 * 	   mtp		- maximum thruput
 * 
 * OUTPUT: ttime	- transfer time in ms
 *
 * RETURN: 0 if ok, 1 if already transfered, -1 if transfer failed
 */
int send_data(int sockfd, unsigned long size, const char *file, 
	      const char *tinfo, const char *iso_name, const char *type, 
	      float mtp, float *ttime) {
  int
    n,			/* simple loop count */
    nblocks,		/* number of packets blocks */
    bn,			/* block number to read */
    percent,		/* what percentage of file has been transmitted */
    ffd;		/* file to send file descriptor */
  unsigned long
    msec,		/* milliseconds of transfer */
    bytes,		/* bytes which has been sent */
    offset;		/* bytes already sent */
  float
    thruput;		/* network thruput */
  char
    packet[OVERSIZE],	/* data packet to send */
    tmp[MAXLEN],	/* temporary string */
    fname[MAXLEN],	/* file name and compress info */
    *reply;		/* reply string */
  time_t 
    sec0,sec1,sec2;	/* unix time */
  struct timeval tv1,tv2;
#if !defined(SOLARIS2) && !defined(IRIX)
  struct timezone tz;
#endif
  extern int 
    quiet,		/* quiet mode flag */
    packet_size;	/* size of a packet in bytes */

  msec=0;
  offset=0;
  
  /* fallback */
  if (packet_size<1) packet_size=512;

  if (ttime) *ttime=0;
  strcpy(fname,iso_name);
  if (*type) {
    strcat(fname," (");
    strcat(fname,type);
    strcat(fname,")");
  }
  if (!quiet) printf("sending...                        \r");
  fflush(stdout);

  /* real sending and no local testing? */
  if (sockfd) {
    
    /* real file to send? ==> ask for resend */
    if (*file) {
    
      sock_putline(sockfd,"RESEND");
      reply=getreply(sockfd);
      
      /* correct answer? */
      if (!str_beq(reply,"500 ") && !str_beq(reply,"502 ")) {
   
	/* error occured? */
	if (!str_beq(reply,"230 ")) {
	  if (quiet<3) {
	    errno=0;
	    snprintf(MAXS(tmp),"server error: %s",&reply[4]);
	    message("",'F',tmp);
	  }
	  return(-1);
	}

	sscanf(&reply[4],"%ld",&offset);

      }
    }

    /* prepare sending of data */
    sock_putline(sockfd,"DATA");
    reply=getreply(sockfd);

    /* file already transmitted? */
    if (str_beq(reply,"531 ")) {
      snprintf(MAXS(tmp),
	       "file %s has been already transmitted - ignored.",iso_name);
      if (quiet<2) message("",'W',tmp);
      return(1);
    }

    /* server reply ok? */
    if (!str_beq(reply,"302 ")) {
      if (quiet<3) {
	snprintf(MAXS(tmp),"corrupt server reply: %s",&reply[4]);
	errno=0;
	message("",'F',tmp);
      }
      return(-1);
    }
  }

  /* open file */
  if (*file) {
    ffd=open(file,O_RDONLY,0);
    if (ffd<0 || lseek(ffd,offset,SEEK_SET)<0) {
      if (quiet<3) {
	snprintf(MAXS(tmp),"error reading %s",iso_name);
	message("",'E',tmp);
      }
      return(-1);
    }
  
  } else
    ffd=fileno(stdin);

  /* resend active? */
  if (offset) {
    snprintf(MAXS(tmp),"resuming %s at byte %ld",iso_name,offset);
    if (quiet<2) message("",'I',tmp);
  }

  if (quiet==1) {
    snprintf(MAXS(tmp),"begin transfer of %s with %lu bytes",fname,size);
    message("",'I',tmp);
  }

  /* get time normal */
#if defined(SOLARIS2) || defined(IRIX)
   #ifdef _SVID_GETTOD
     gettimeofday(&tv1);
   #else
     gettimeofday(&tv1,NULL);
   #endif
#else
  gettimeofday(&tv1,&tz);
#endif
  sec1=0;
  sec0=time(0);
  
  /* send the file data in packets */
  bytes=0;
  size=size-offset;
  nblocks=size/packet_size;
  for (bn=1; bn<=nblocks; bn++) {
    if (readn(ffd,packet,packet_size)<packet_size) {
      if (quiet<3) {
        if (!quiet) printf("\n");
	snprintf(MAXS(tmp),"error reading %s",iso_name);
	message("",'E',tmp);
      }
      close(ffd);
      return(-1);
    }
    if (sockfd && writen(sockfd,packet,packet_size)<packet_size) {
      if (quiet<3) {
        if (!quiet) printf("\n");
	errno=0;
	message("",'F',"error sending data");
      }
      close(ffd);
      return(-1);
    }

    /* print transaction message once a second */
    bytes+=packet_size;
    percent=(bytes+offset)*100.0/(size+offset);
    if (!quiet) {
      sec2=time(0);
      if (sec2>sec1) {
	fprintf(stderr,"%s%s: %3d%%  (%ld of %ld kB)\r",
		tinfo,fname,percent,
		(bytes+offset-1)/1024+1,(size+offset-1)/1024+1);
	fflush(stderr);
	sec1=sec2;
      }
    }
    
    /* limit maximum thruput */
    if (mtp) while (bytes/(time(0)-sec0+0.001)>mtp*1024) sleep(1);
    
  }
  
  /* send the last bytes */
  if ((n=size-nblocks*packet_size) > 0) {
    if (readn(ffd,packet,n)<n) {
      if (quiet<3) {
        snprintf(MAXS(tmp),"error reading %s",iso_name);
	message("",'E',tmp);
      }
      close(ffd);
      return(-1);
    }
    if (sockfd && writen(sockfd,packet,n)<n) {
      if (quiet<3) {
	errno=0;
        message("",'F',"error sending data");
      } else
        return(-1);
    }
    bytes+=n;
  }

  close(ffd);

  if (quiet<2) {
   
    /* get time difference */
#if defined(SOLARIS2) || defined(IRIX)
   #ifdef _SVID_GETTOD
     gettimeofday(&tv2);
   #else
     gettimeofday(&tv2,NULL);
   #endif
#else
    gettimeofday(&tv2,&tz);
#endif
    msec=(tv2.tv_usec-tv1.tv_usec)/1000+(tv2.tv_sec-tv1.tv_sec)*1000;
    if (msec<1) msec=1;
    thruput=bytes*1000.0/msec;

    /* print transfer statistics */
    if (quiet==1) {
      
      if (thruput>9999)
	snprintf(MAXS(tmp),
		 "transfer of %s completed: %.1f kB/s",fname,thruput/1024);
      else
	snprintf(MAXS(tmp),
		 "transfer of %s completed: %d byte/s",fname,(int)thruput);
      message("",'I',tmp);

    } else {
      
      fprintf(stderr,"%s%s: 100%%  (",tinfo,fname);
      if (bytes>9999)
	fprintf(stderr,"%ld kB, ",bytes/1024);
      else
	fprintf(stderr,"%ld byte, ",bytes);
      if (thruput>9999)
	fprintf(stderr,"%.1f kB/s)            \n",thruput/1024);
      else
	fprintf(stderr,"%d byte/s)            \n",(int)thruput);
      fflush(stderr);
    }
  }

  if (ttime) *ttime=(float)msec;

  /* transfer ok? */
  if (sockfd && !str_beq(getreply(sockfd),"201 ")) {
    if (quiet<3) {
      snprintf(MAXS(tmp),"transfer failed for %s",iso_name);
      errno=0;
      message("",'E',tmp);
    }
    return(-1);
  }

  return(0);
}


syntax highlighted by Code2HTML, v. 0.9.1