/*****************************************************************************\
* Copyright (c) 2002-2003 Pelle Johansson.                                    *
* All rights reserved.                                                        *
*                                                                             *
* This file is part of the moftpd package. Use and distribution of            *
* this software is governed by the terms in the file LICENCE, which           *
* should have come with this package.                                         *
\*****************************************************************************/

/* $moftpd: transfer.c 1264 2005-04-06 13:32:27Z morth $ */

#include "system.h"

#include "connection.h"

#include "utf8fs/memory.h"
#include "main.h"
#include "accounter.h"
#include "events.h"

int xferBufferSize = 1024;
char *xferBuffer;

extern int forkConnections, urgData, forker;

int passive_acceptor(int sock, void *user, int urgent)
{
  connection_t *conn = user;
  struct sockaddr_storage addr;
  int al = sizeof(addr);
  int i;
  
  if (conn->sock == -1)
    return 0;
  
  set_locale (conn->currLang);
  remove_read_fd(sock);
  conn->dataSock = accept(sock, (struct sockaddr*)&addr, &al);
  if(conn->dataSock == -1)
  {
    reply(conn, "451 Acception of connection failed: %s.", strerror(errno));
    close_data_connection (conn);
    close(sock);
    return 0;
  }
  close(sock);
  if(!conn->user->allowForeign)
  {
    if (!same_addr((struct sockaddr*)&addr, (struct sockaddr*)&conn->rDataAddr, 0))
    {
      syslog (LOG_NOTICE, "%d: Data connection from foreign address", conn->id);
      reply(conn, "501 Foreign address not allowed.");
      close_data_connection (conn);
      return 0;
    }
  }
  else if (!conn->user->allowOutOfRange && conn->server->numRanges)
  {
    for (i = 0; i < conn->server->numRanges; i++)
    {
      if (check_range ((struct sockaddr*)&addr,
		(struct sockaddr*)&conn->server->ranges[i].addr,
		(struct sockaddr*)&conn->server->ranges[i].mask))
	break;
    }
    if (i == conn->server->numRanges)
    {
      syslog (LOG_NOTICE, "%d: Data connection out of range", conn->id);
      reply (conn, "501 Address out of allowed range.");
    }
  }
  if(fcntl(conn->dataSock, F_SETFL, O_NONBLOCK))
  {
    reply(conn, "451 Failed to make socket nonblocking: %s.", strerror(errno));
    close_data_connection (conn);
    return 0;
  }
  i = IPTOS_THROUGHPUT;
  setsockopt(conn->dataSock, IPPROTO_IP, IP_TOS, &i, sizeof(i));
  conn->passiveAccepted = 1;
  if(conn->working && open_data_connection(conn))
  {
    reply(conn, "421 %s.", strerror(errno));
    close_data_connection (conn);
  }
  return 0;
}

int open_data_connection(connection_t *conn)
{
  int i;
  
  if(!xferBuffer)
  {
    xferBuffer = palloc(xferBufferSize, NULL, NULL);
    if(!xferBuffer)
      return -1;
  }
  
  if(conn->passive)
  {
    if(!conn->passiveAccepted)
      return 0;
  }
  else
  {
    if(conn->dataSock >= 0)
    {
      close(conn->dataSock);
      remove_read_fd(conn->dataSock);
      remove_write_fd(conn->dataSock);
    }
    conn->dataSock = socket(conn->rDataAddr.ss_family, SOCK_STREAM,
	  IPPROTO_TCP);
    if(conn->dataSock < 0)
      return -1;
    
    if(fcntl(conn->dataSock, F_SETFL, O_NONBLOCK))
    {
      close(conn->dataSock);
      conn->dataSock = -1;
      return -1;
    }
    
    super_privs(0);
    
    switch(conn->rDataAddr.ss_family)
    {
    case AF_INET:
      if(conn->lDataAddr.ss_family == AF_INET)
      {
	((struct sockaddr_in*)&conn->lDataAddr)->sin_port = htons (conn->activePort);
	bind(conn->dataSock, (struct sockaddr*)&conn->lDataAddr,
	      sizeof(struct sockaddr_in));
      }
      else if(conn->activePort)
      {
	struct sockaddr_in addr = {};
	
	addr.sin_family = AF_INET;
#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN
	addr.sin_len = sizeof(addr);
#endif
	addr.sin_port = htons (conn->activePort);
	bind(conn->dataSock, (struct sockaddr*)&addr, sizeof(addr));
      }
      if(connect(conn->dataSock, (struct sockaddr*)&conn->rDataAddr,
	    sizeof(struct sockaddr_in)) && errno != EINPROGRESS)
	return -1;
      break;
    case AF_INET6:
      if(conn->lDataAddr.ss_family == AF_INET6)
      {
	((struct sockaddr_in6*)&conn->lDataAddr)->sin6_port = htons (conn->activePort);
	bind(conn->dataSock, (struct sockaddr*)&conn->lDataAddr, 
	      sizeof(struct sockaddr_in6));
      }
      else if(conn->activePort)
      {
	struct sockaddr_in6 addr = {};
	
	addr.sin6_family = AF_INET6;
#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN
	addr.sin6_len = sizeof(addr);
#endif
	addr.sin6_port = htons (conn->activePort);
	bind(conn->dataSock, (struct sockaddr*)&addr, sizeof(addr));
      }
      if(connect(conn->dataSock, (struct sockaddr*)&conn->rDataAddr,
	    sizeof(struct sockaddr_in6)) && errno != EINPROGRESS)
	return -1;
      break;
    default:
      errno = EINVAL;
      return -1;
    }
  }
  i = IPTOS_THROUGHPUT;
  setsockopt(conn->dataSock, IPPROTO_IP, IP_TOS, &i, sizeof(i));
  i = xferBufferSize;
  setsockopt(conn->dataSock, SOL_SOCKET, SO_SNDLOWAT, &i, sizeof(i));
  conn->transferStart = 0;
#ifdef SO_NOSIGPIPE
  i = -1;
  setsockopt (conn->dataSock, SOL_SOCKET, SO_NOSIGPIPE, &i, sizeof (i));
#endif
  conn->dataWrite = conn_plain_writer;
  conn->dataChannel = (void*)conn->dataSock;
#ifdef USE_TLS
  conn->tlsDataState = 0;
  if (conn->protLevel)
  {
    conn->tlsData = tls_open (conn->dataSock, conn->server->tlsOptions,
          conn->server->tlsCert, conn->server->tlsKey);
    if (!conn->tlsData)
    {
      reply (conn, "431 TLS initialisation failed.");
      close_data_connection (conn);
      return 0;
    }
    tls_start (conn->tlsData);
    add_read_fd (conn->dataSock, data_receiver, conn);
    return 0;
  }
#endif
  if (conn->sending)
  {
    /*
     * Block on sending if forking connections. We should get EINTR on SIGURG,
     * and since we set SNDLOWAT to the xfer buffer size it should only apply
     * to mmap() sends.
     */
    if (forkConnections && forker != getpid ())
      fcntl (conn->dataSock, F_SETFL, 0);
    add_write_fd (conn->dataSock, data_sender, conn);
  }
  else
    add_read_fd (conn->dataSock, data_receiver, conn);
  return 0;
}

int close_data_connection (connection_t *conn)
{
  int res = 0;
  
  if (conn->filePath)
  {
    time_t now;
    char rhost[NI_MAXHOST];
    int al;
    
    switch (conn->rDataAddr.ss_family)
    {
    case AF_INET:
      al = sizeof (struct sockaddr_in);
      break;
    case AF_INET6:
      al = sizeof (struct sockaddr_in6);
      break;
    default:
      al = sizeof (conn->rDataAddr);
      break;
    }
    
    if (getnameinfo ((struct sockaddr*)&conn->rDataAddr, al, rhost,
	      sizeof (rhost), NULL, 0, NI_NUMERICHOST))
      strcpy (rhost, "unknown");
    
    time (&now);
    /*
     * Fields are:
     * connection id (as usual):
     * current time
     * transfer time in seconds
     * remote host
     * transfered bytes
     * file path
     * transfer mode, a for ascii, b for binary
     * Special action, _ for no action, S for secured data connection.
     * transfer direction, o for outgoing, i for incoming
     * user type, a for anonymous, g (guest) for server user,
     *   r (real) for system user.
     * user name, email for anonymous users, account if entered, otherwise
     *   user.
     * Service name, FTPS for a secure connection, FTP for nonsecure.
     *   This reflects the control connection, not the data connection.
     * authentication-method, always 0 for none right now.
     * authenticated-user-id, always * for n/a right now.
     * success flag, c for complete transfer, i for incomplete.
     */
    if (!conn->data)
      conn->restart = 0;
    syslog (LOG_INFO, "%d: %.24s %.0f %s %llu %s %c %c %c %c %s %s 0 * %c",
	  conn->id, ctime (&now), difftime (now, conn->transferStart), rhost,
	  conn->dataOffset - conn->restart, conn->filePath,
	  conn->type == ttText? 'a' : 'b',
#ifdef USE_TLS
	  conn->tlsData? 'S' :
#endif
	  '_',
	  conn->sending? 'o' : 'i',
	  conn->user && conn->user->anonymous? 'a' :
	  conn->account || (conn->user && conn->user->spec_flags)? 'g' : 'r',
	  conn->email? conn->email :
	  conn->account? conn->account :
	  conn->user? conn->user->name : "unknown",
#ifdef USE_TLS
	  conn->tlsControl? "FTPS" :
#endif
	  "FTP",
	  conn->dataOffset >= conn->dataLen? 'c' : 'i');
    if (conn->sending)
      accounter (conn->accSock, "SENT %lld %s", conn->dataOffset - conn->restart, conn->filePath);
    else
      accounter (conn->accSock, "GOT %lld %s", conn->dataOffset, conn->filePath);
    pfree (conn->filePath, conn);
    conn->filePath = NULL;
  }
  conn->passive = 0;
  conn->passiveAccepted = 0;
  conn->working = 0;
  if(conn->data)
  {
    if(conn->fileFd >= 0)
      munmap(conn->data, conn->dataLen);
    else
      pfree(conn->data, conn);
    conn->data = NULL;
  }
  conn->dataOffset = 0;
  conn->dataLen = 0;
  if(conn->fileFd >= 0)
  {
    close(conn->fileFd);
    conn->fileFd = -1;
  }
#ifdef USE_TLS
  if (conn->tlsData)
  {
    tls_stop (conn->tlsData);
    tls_free (conn->tlsData);
    conn->tlsData = NULL;
  }
#endif
  if(conn->dataSock >= 0)
  {
    close(conn->dataSock);
    remove_read_fd(conn->dataSock);
    remove_write_fd(conn->dataSock);
    conn->dataSock = -1;
    res = 1;
  }
  if(conn->spontQuit)
  {
    conn->spontQuit = 0;
    disconnected(conn);
    return -1;
  }
  return res;
}

int check_secure_data (connection_t *conn)
{
#ifdef USE_TLS
  int l;
  
  if (conn->tlsData)
  {
    switch (conn->tlsDataState)
    {
    case 0:
      l = tls_accept (conn->tlsData);
      if (l == 1)
      {
	syslog (LOG_INFO, "%d: Successfully negotiated data TLS.", conn->id);
	conn->tlsDataState = 1;
	conn->dataWrite = (writeFun_t)tls_write;
	conn->dataChannel = conn->tlsData;
	if (conn->sending)
	{
	  remove_read_fd (conn->dataSock);
	  add_write_fd (conn->dataSock, data_sender, conn);
	}
	if (conn->server->tlsOptions & tlsVerifyClient)
	{
	  tlscert_t clientDataCert = tls_get_peer_cert (conn->tlsData);
	  
	  if (tls_compare_certs (conn->tlsClientCert, clientDataCert))
	  {
	    syslog (LOG_NOTICE, "%d: Control/data certificate mismatch.", conn->id);
	    reply (conn, "535 Must use same certificate on control and data connections.");
	    close_data_connection (conn);
	  }
	  tls_free_cert (clientDataCert);
	}
      }
      else if (l)
      {
	syslog (LOG_NOTICE, "%d: Error in data TLS negotiation: %s.", conn->id,
	      tls_error (conn->tlsData, l));
	close_data_connection (conn);
      }
      return 1;
    case 2:
      l = tls_stop (conn->tlsData);
      if (l == 1)
	close_data_connection (conn);
      else if (l)
      {
	syslog (LOG_NOTICE, "%d: Error in TLS shutdown: %s.", conn->id,
	      tls_error (conn->tlsData, l));
	close_data_connection (conn);
      }
      return 1;
    }
  }
#endif
  return 0;
}

int data_receiver(int sock, void *user, int urgent)
{
  connection_t *conn = user;
  int rl, wl;
  char *bp1, *bp2;
  
  if (conn->sock == -1)
    return 0;
  
  set_locale (conn->currLang);
  if (conn->protLevel && check_secure_data (conn))
    return 0;
  
  if (!conn->transferStart)
    time (&conn->transferStart);
  
#ifdef USE_TLS
  if (conn->tlsData)
    rl = tls_read (conn->tlsData, xferBuffer, xferBufferSize);
  else
#endif
    rl = recv(sock, xferBuffer, xferBufferSize, 0);
  if(rl < 0)
  {
    reply(conn, "451 socket error: %s.",
#ifdef USE_TLS
	  conn->tlsData? tls_error (conn->tlsData, rl) :
#endif
	  strerror(errno));
    close_data_connection (conn);
    return 0;
  }
  if(!rl)
  {
    /* EOF */
    if (!conn->dataLen)
      conn->dataLen = conn->dataOffset;
    reply(conn, "226 Transfer complete.");
    close_data_connection (conn);
    return 0;
  }
  if(conn->type == ttText)
  {
    wl = 0;
    for(bp1 = xferBuffer; (bp2 = memchr(bp1, '\r', rl)); bp1 = bp2 + 1)
    {
      wl = write(conn->fileFd, bp1, bp2 - bp1);
      if(wl == -1)
	break;
      conn->dataOffset += wl + 1;
      rl -= wl + 1;
    }
    if(rl && wl != -1)
    {
      wl = write(conn->fileFd, bp1, rl);
      conn->dataOffset += rl;
    }
  }
  else
  {
    wl = write(conn->fileFd, xferBuffer, rl);
    conn->dataOffset += rl;
  }
  if(wl == -1)
  {
    if(errno == ENOSPC)
      reply(conn, "452 Out of disk space.");
    else if(errno == EDQUOT)
      reply(conn, "552 Quota exceeded.");
    else
      reply(conn, "451 write failed: %s.", strerror(errno));
    conn->dataLen = conn->dataOffset + 1; // To indicate "incomplete"
    close_data_connection (conn);
  }
  return 0;
}

int data_sender(int sock, void *user, int urgent)
{
  connection_t *conn = user;
  int rl, wl, fc;
  char *bp1, *bp2;
  
  if (conn->sock == -1)
    return 0;
  
  set_locale (conn->currLang);
  if (!conn->transferStart)
    time (&conn->transferStart);
  
  rl = sizeof(errno);
  if(!getsockopt(sock, SOL_SOCKET, SO_ERROR, &errno, &rl))
  {
    if(errno)
    {
      if (errno == EPIPE)
	reply (conn, "451 Remote end closed connection.");
      else
	reply (conn, "451 socket error: %s.", strerror (errno));
      close_data_connection (conn);
      return 0;
    }
  }
  
#ifdef USE_TLS
  if (conn->tlsControl)
    urgData = 1; // Force exit after one loop.
#endif
  fc = (forkConnections && forker != getpid ()); // Store locally so a smart compiler knows it doesn't change.
  
  if(conn->data)
  {
    do
    {
      if(conn->dataOffset == conn->dataLen)
      {
	/* EOF */
	reply(conn, "226 Transfer complete.");
	close_data_connection (conn);
	return 0;
      }
      wl = conn->dataWrite (conn->dataChannel, (char*)conn->data + conn->dataOffset,
	    conn->dataLen - conn->dataOffset);
      if(wl >= 0)
	conn->dataOffset += wl;
      else
      {
#ifdef USE_TLS
	if (conn->tlsData)
	{
	  reply (conn, "451 Write to socket failed: %s.", 
		tls_error (conn->tlsData, wl));
	  close_data_connection (conn);
	}
	else
#endif
	  if (errno != EINTR && errno != EAGAIN)
	  {
	    if (errno == EPIPE)
	      reply (conn, "451 Remote end closed connection.");
	    else
	      reply (conn, "451 Write to socket failed: %s.", strerror(errno));
	    close_data_connection (conn);
	  }
	return 0;
      }
    } while(fc && !urgData);
  }
  else if(conn->type == ttText)
  {
    do
    {
      if (conn->endOffset)
      {
	rl = conn->endOffset - conn->restart - conn->dataOffset;
	if (rl > xferBufferSize)
	  rl = xferBufferSize;
	else if (rl < 0)
	  rl = 0;
      }
      else
	rl = xferBufferSize;
      if (rl)
	rl = read(conn->fileFd, xferBuffer, rl);
      if(rl == -1)
      {
	reply(conn, "451 read failed: %s.", strerror(errno));
	close_data_connection (conn);
	return 0;
      }
      if(!rl)
      {
	/* EOF */
	conn->dataLen = conn->dataOffset;
	reply(conn, "226 Transfer complete.");
	close_data_connection (conn);
	return 0;
      }
      conn->dataOffset += rl;
      wl = 0;
      for(bp1 = xferBuffer; (bp2 = memchr(bp1, '\n', rl)); bp1 = bp2 + 1)
      {
	wl = conn->dataWrite (conn->dataChannel, bp1, bp2 - bp1);
	if(wl < 0)
	  break;
	rl -= wl + 1;
	wl = conn->dataWrite (conn->dataChannel, "\r\n", 2);
	if(wl < 0)
	  break;
	conn->dataOffset++;
      }
      if (rl && wl >= 0)
	wl = conn->dataWrite (conn->dataChannel, bp1, rl);
      if (wl < 0)
      {
#ifdef USE_TLS
	if (conn->tlsData)
	{
	  reply (conn, "451 Write to socket failed: %s.", 
		tls_error (conn->tlsData, wl));
	  close_data_connection (conn);
	}
	else
#endif
	  if (errno != EINTR && errno != EAGAIN)
	  {
	    if (errno == EPIPE)
	      reply (conn, "451 Remote end closed connection.");
	    else
	      reply (conn, "451 Write to socket failed: %s.", strerror(errno));
	    close_data_connection (conn);
	  }
	  else
	  {
	    lseek (conn->fileFd, SEEK_CUR, -rl);
	    conn->dataOffset -= rl;
	  }
	return 0;
      }
    } while(fc && !urgData);
  }
  else
  {
    do
    {
      if (conn->endOffset)
      {
	rl = conn->endOffset - conn->restart - conn->dataOffset;
	if (rl > xferBufferSize)
	  rl = xferBufferSize;
	else if (rl < 0)
	  rl = 0;
      }
      else
	rl = xferBufferSize;
      rl = read(conn->fileFd, xferBuffer, rl);
      if(rl == -1)
      {
	reply(conn, "451 read failed: %s.", strerror(errno));
	close_data_connection (conn);
	return 0;
      }
      if(!rl)
      {
	/* EOF */
	conn->dataLen = conn->dataOffset;
	reply(conn, "226 Transfer complete.");
	close_data_connection (conn);
	return 0;
      }
      conn->dataOffset += rl;
      wl = conn->dataWrite (conn->dataChannel, xferBuffer, rl);
      if (wl < 0)
      {
#ifdef USE_TLS
	if (conn->tlsData)
	{
	  reply (conn, "451 Write to socket failed: %s.", 
		tls_error (conn->tlsData, wl));
	  close_data_connection (conn);
	}
	else
#endif
	  if (errno != EINTR && errno != EAGAIN)
	  {
	    if (errno == EPIPE)
	      reply (conn, "451 Remote end closed connection.");
	    else
	      reply (conn, "451 Write to socket failed: %s.", strerror (errno));
	    close_data_connection (conn);
	  }
	  else
	  {
	    lseek (conn->fileFd, SEEK_CUR, -rl);
	    conn->dataOffset -= rl;
	  }
	return 0;
      }
      else if(wl < rl)
      {
	lseek(conn->fileFd, SEEK_CUR, wl - rl);
	conn->dataOffset += wl - rl;
      }
    } while(fc && !urgData);
  }
  return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1