/*
 * Multiplexing File Server Copyright (c) 2018, 2019, 2022, 2025, James Bailie.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *     * The name of James Bailie may not be used to endorse or promote
 * products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/event.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <sys/un.h>

#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>

#include <pwd.h>
#include <grp.h>

#include <syslog.h>
#include <stdarg.h>
#include <string.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>

#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/dh.h>
#include <openssl/ec.h>
#include <openssl/bio.h>
#include <openssl/x509.h>
#include <openssl/crypto.h>
#include <openssl/tls1.h>

#define MAX_FRAME 32766

/*
 * KQueue Input and output queues.
 */

#define QLEN 1024
#define MAX_EVENT 10

SSL_CTX *ctx;

struct kevent *fs_inqueue, *fs_outqueue;

extern char *optarg;

int fs_idx = 0, fs_in = 0, fs_out = 0, fs_fd = -1,
    fs_timer = 0, fs_max_conn = QLEN, fs_qlen = QLEN * MAX_EVENT,
    fs_max_requests = 255, fs_serial = 0;

unsigned int fs_stack_inc = 128, fs_backlog = 1000, fs_conn_count = 0;

char *fs_grp = "nobody", *fs_user = "nobody",
     *fs_interface = "", *fs_port = "8000", *fs_tls_file = NULL, *fs_rootdir = NULL,
     *fs_keyfile = NULL, *fs_chainfile = NULL, *fs_password = NULL,
     *fs_pidfile = "/var/run/multifile.pid", *fs_app_name = "multifile";

void ( *fs_periodic )() = NULL;

struct passwd *fs_passwd;
struct group *fs_group;

volatile int fs_sigterm = 0;

/*
 * Queue of buffered out-going data for each connection.
 */

struct fs_queue
{
   int total, offset, sendfile, len;
   unsigned char *buffer;
   struct fs_queue *next;
};

/*
 * Connection control block.
 */

struct fs_ccb
{
   SSL *ssl;
   int sock, total, stage, opposite, ktls;
   unsigned int len, offset, qlen;
   struct fs_queue *first, *last;
   void *data;
   unsigned char *buffer, size[ 2 ];
};

struct fs_string
{
   int free, used;
   char *top;
   char *str;
};

void fs_set_periodic( void (*)(), int );

int fs_queue_sendfile_frame( struct fs_ccb *, unsigned int, unsigned char *, int, ssize_t, ssize_t );
int fs_queue_frame( struct fs_ccb *, unsigned int, unsigned char * );

void fs_init_func();
void fs_exit_func();

void fs_set_events( struct fs_ccb *, int, int );
int fs_accept_callback( void * );
int fs_open_callback( void * );
void fs_close_callback( void * );
void fs_read_callback( void *, unsigned int, unsigned char * );
void fs_write_callback( void * );

void fs_log_error()
{
   unsigned long err;
   char buffer[ 128 ];

   while(( err = ERR_get_error()))
   {
      *buffer = '\0';
      ERR_error_string_n( err, buffer, sizeof( buffer ));
      syslog( LOG_ERR, "%s: OpenSSL: %s", fs_app_name, buffer );
   }
}

void *fs_memory( size_t size )
{
   void *ptr;

   if ( size == 0 )
      return NULL;

   if (( ptr = malloc( size )) == NULL )
   {
      syslog( LOG_WARNING, "malloc(): %m" );
      return NULL;
   }

   return ptr;
}

char *fs_str_dup( char *str )
{
   char *ptr;
   int len;

   for( len = 0, ptr = str; *ptr; ++ptr )
      ++len;

   if (( ptr = fs_memory( len + 1 )) != NULL )
   {
      bcopy( str, ptr, len );
      ptr[ len ] = '\0';
   }

   return ptr;
}

void fs_ev_set( int desc, short filter, u_short flags, struct fs_ccb *item )
{
   struct kevent *kev;

   if ( fs_in >= fs_qlen )
      return;

   kev = &fs_inqueue[ fs_in++ ];

   kev->ident = desc;
   kev->filter = filter;
   kev->fflags = 0;
   kev->flags = flags;
   kev->udata = item;
}

void fs_stop_read_socket( struct fs_ccb *item )
{
   fs_ev_set( item->sock, EVFILT_READ, EV_DISABLE, item );
}

void fs_start_read_socket( struct fs_ccb *item )
{
   fs_ev_set( item->sock, EVFILT_READ, EV_ENABLE, item );
}

void fs_stop_write_socket( struct fs_ccb *item )
{
   fs_ev_set( item->sock, EVFILT_WRITE, EV_DISABLE, item );
}

void fs_start_write_socket( struct fs_ccb *item )
{
   fs_ev_set( item->sock, EVFILT_WRITE, EV_ENABLE, item );
}

void fs_set_timer()
{
   struct kevent *kev;

   if ( fs_in >= fs_qlen )
      return;

   kev = &fs_inqueue[ fs_in++ ];

   kev->ident = 1;
   kev->filter = EVFILT_TIMER;
   kev->fflags = 0;
   kev->data = fs_timer * 1000;
   kev->flags = EV_ADD;
   kev->udata = NULL;
}

void fs_delete_timer()
{
   struct kevent *kev;

   if ( fs_in >= fs_qlen )
      return;

   kev = &fs_inqueue[ fs_in++ ];

   kev->ident = 1;
   kev->filter = EVFILT_TIMER;
   kev->fflags = 0;
   kev->data = 0;
   kev->flags = EV_DELETE;
   kev->udata = NULL;
}

void fs_set_periodic( void ( *callback )(), int seconds )
{
   if (( fs_timer = seconds ) <= 0 || callback == NULL )
      fs_timer = 0;

   fs_delete_timer();

   if ( fs_timer )
      fs_set_timer();

   fs_periodic = callback;
}

void fs_non_blocking( int desc )
{
   int flags, unblocked;

   if (( flags = fcntl( desc, F_GETFL, 0 )) < 0 )
   {
      syslog( LOG_ERR, "fcntl(): %m" );
      exit( 1 );
   }

   unblocked = flags & O_NONBLOCK;

   if ( ! unblocked && fcntl( desc, F_SETFL, flags | O_NONBLOCK ) < 0 )
   {
      syslog( LOG_ERR, "fcntl(): %m" );
      exit( 1 );
   }
}

void fs_set_options( int argc, char **argv )
{
   int i;

   while(( i = getopt( argc, argv, "sr:i:p:m:n:u:g:t:" )) != -1 )
      switch( i )
      {
         case 'g':
            fs_grp = optarg;
            break;

         case 'i':
            fs_interface = optarg;
            break;

         case 'm':
            fs_max_conn = strtol( optarg, NULL, 10 );
            break;

         case 'n':
            fs_max_requests = strtol( optarg, NULL, 10 );
            break;

         case 'p':
            fs_port = optarg;
            break;

         case 'r':
            fs_rootdir = optarg;
            break;

         case 's':
            ++fs_serial;
            break;

         case 'u':
            fs_user = optarg;
            break;

         case 't':
            fs_tls_file = optarg;
            break;
      }

   if ( fs_tls_file == NULL )
   {
      syslog( LOG_ERR, "-t option missing" );
      exit( 1 );
   }

   if ( fs_rootdir == NULL )
   {
      syslog( LOG_ERR, "-r option missing" );
      exit( 1 );
   }

   if ( fs_max_conn <= 0 )
   {
      syslog( LOG_ERR, "-m <max-conn> <= 0: %d", fs_max_conn );
      exit( 1 );
   }

   if ( fs_max_requests < 1 || fs_max_requests > 255 )
   {
      syslog( LOG_ERR, "-n <max-requests> out of range 1-255: %d", fs_max_requests );
      exit( 1 );
   }

   if (( fs_passwd = getpwnam( fs_user )) == NULL )
   {
      syslog( LOG_ERR, "user \"%s\" does not exist", fs_user );
      exit( 1 );
   }

   if (( fs_group = getgrnam( fs_grp )) == NULL )
   {
      syslog( LOG_ERR, "group \"%s\" does not exist", fs_grp );
      exit( 1 );
   }
}

void fs_change_identity()
{
   if ( setgid( fs_group->gr_gid ) < 0 )
      syslog( LOG_ERR, "setgid(): %m" );

   if ( setuid( fs_passwd->pw_uid ) < 0 )
      syslog( LOG_ERR, "setuid(): %m" );
}

void fs_close_on_exec( int fd )
{
   if ( fcntl( fd, F_SETFD, FD_CLOEXEC ) < 0 )
      syslog( LOG_ERR, "fcntl( F_SETFD, FD_CLOEXEC ): %m" );
}

void fs_start_listening()
{
   struct addrinfo hints, *res;
   int result;

   /*
    * This is the new, protocol-independent way of setting up a listening
    * socket.
    */

   bzero( &hints, sizeof( struct addrinfo ));
   hints.ai_flags = AI_PASSIVE;
   hints.ai_socktype = SOCK_STREAM;

   /*
    * If the user has not specified an interface, we listen on the
    * IPv6 wildcard address.
    */

   if (( result = getaddrinfo(( *fs_interface ? fs_interface : NULL ), fs_port, &hints, &res )))
   {
      syslog( LOG_ERR, "getaddrinfo(): %s", gai_strerror( result ));
      exit( 1 );
   }

   if ( res == NULL )
   {
      syslog( LOG_ERR, "getaddrinfo(): no interface found" );
      exit( 1 );
   }

   fs_fd = socket( res->ai_family, res->ai_socktype, res->ai_protocol );

   if ( fs_fd == -1 )
   {
      syslog( LOG_ERR, "socket(): %m" );
      exit( 1 );
   }

   result = 1;

   /*
    * Allow duplicate bindings, so we can have more than one
    * server process, if necessary.
    */

   if ( setsockopt( fs_fd, SOL_SOCKET, SO_REUSEPORT, &result, sizeof( result )) < 0 )
      syslog( LOG_WARNING, "setsockopt( SO_REUSEPORT ): %m" );

   /*
    * Try and detect connections which go idle for long periods of time.
    */

   if ( setsockopt( fs_fd, SOL_SOCKET, SO_KEEPALIVE, &result, sizeof( result )) < 0 )
      syslog( LOG_WARNING, "setsockopt( SO_KEEPALIVE ): %m" );

   /*
    * Make sure we can accept IPv4 traffic as well as IPv6, if we
    * are bound to the IPv6 wildcard address.  We are turning off
    * the option because result = 0.
    */

   result = 0;

   if ( ! *fs_interface && setsockopt( fs_fd, IPPROTO_IPV6, IPV6_BINDV6ONLY, &result, sizeof( result )) < 0 )
      syslog( LOG_WARNING, "setsockopt( IPV6_BINDV6ONLY ): %m" );

   if ( bind( fs_fd, res->ai_addr, res->ai_addrlen ) < 0 )
   {
      syslog( LOG_ERR, "bind(): %m" );
      exit( 1 );
   }

   if ( res != NULL )
      freeaddrinfo( res );

   if ( listen( fs_fd, fs_backlog ) < 0 )
   {
      syslog( LOG_ERR, "listen(): %m" );
      exit( 1 );
   }

   fs_close_on_exec( fs_fd );
   fs_non_blocking( fs_fd );
}

void fs_set_data( struct fs_ccb *item, void *data )
{
   if ( item != NULL )
      item->data = data;
}

void *fs_get_data( struct fs_ccb *item )
{
   if ( item != NULL )
      return item->data;

   return NULL;
}

int fs_queue_sendfile_frame( struct fs_ccb *item, unsigned int dlen, unsigned char *data, int fd, ssize_t offset, ssize_t flen )
{
   struct fs_queue *q;
   unsigned char *buffer;

   if (( dlen + flen ) > MAX_FRAME )
      return -2;

   if (( buffer = fs_memory( dlen + 2 )) == NULL )
      return -1;

   buffer[ 0 ] = ( dlen + flen ) / 256;
   buffer[ 1 ] = ( dlen + flen ) % 256;
   bcopy( data, &buffer[ 2 ], dlen );

   if (( q = fs_memory( sizeof( struct fs_queue ))) == NULL )
   {
      free( buffer );
      return 1;
   }

   ++item->qlen;

   q->buffer   = buffer;
   q->total    = dlen + 2;
   q->next     = NULL;

   q->sendfile = fd;
   q->offset   = offset;
   q->len      = flen;

   if ( item->first == NULL )
   {
      item->first = item->last = q;
      fs_start_write_socket( item );
   }
   else
   {
      item->last->next = q;
      item->last = q;
   }

   return 0;
}

int fs_queue_frame( struct fs_ccb *item, unsigned int len, unsigned char *data )
{
   struct fs_queue *q;
   unsigned char *buffer;

   if ( len > MAX_FRAME )
      return -2;

   if (( buffer = fs_memory( len + 2 )) == NULL )
      return -1;

   buffer[ 0 ] = len / 256;
   buffer[ 1 ] = len % 256;

   bcopy( data, &buffer[ 2 ], len );

   if (( q = fs_memory( sizeof( struct fs_queue ))) == NULL )
   {
      free( buffer );
      return 1;
   }

   bzero( q, sizeof( struct fs_queue ));
   q->buffer = buffer;
   ++item->qlen;

   if ( item->first == NULL )
   {
      item->first = item->last = q;
      fs_start_write_socket( item );
   }
   else
   {
      item->last->next = q;
      item->last = q;
   }

   item->last->next = NULL;
   item->last->total = len + 2;

   return 0;
}

void fs_clear_events( struct fs_ccb *item )
{
   int i;

   for( i = fs_idx + 1; i < fs_out; ++i )
      if ( fs_outqueue[ i ].udata == item )
         fs_outqueue[ i ].ident = 0;
}

void fs_free_conn( struct fs_ccb *item )
{
   fs_clear_events( item );
   --fs_conn_count;
   fs_close_callback( item );

   if ( item->ssl != NULL )
   {
      SSL_shutdown( item->ssl );
      SSL_free( item->ssl );
   }

   close( item->sock );

   if ( item->buffer != NULL )
      free( item->buffer );

   while( item->first != NULL )
   {
      item->last = item->first->next;

      if ( item->first->buffer != NULL )
         free( item->first->buffer );

      free( item->first );
      item->first = item->last;
   }

   free( item );
}

void fs_ssl_accept( struct fs_ccb *item )
{
   int err;
   BIO *bio;

   if (( err = SSL_accept( item->ssl )) <= 0 )
   {
      fs_set_events( item, err, -1 );
      return;
   }

   fs_stop_write_socket( item );
   fs_start_read_socket( item );

   if ( fs_open_callback( item ))
   {
      fs_close_callback( item );
      fs_free_conn( item );
      return;
   }

   if (( bio = SSL_get_wbio( item->ssl )) != NULL && BIO_get_ktls_send( bio ))
      ++item->ktls;

   ++item->stage;
}

int fs_has_ktls( struct fs_ccb *item )
{
   return item->ktls;
}

void fs_add_conn( int new )
{
   struct fs_ccb *ptr;

   if (( ptr = fs_memory( sizeof( struct fs_ccb ))) == NULL )
   {
      close( new );
      return;
   }

   bzero( ptr, sizeof( struct fs_ccb ));
   fs_close_on_exec( new );

   ptr->sock = new;
   ptr->stage = -1;

   if (( ptr->ssl = SSL_new( ctx )) == NULL )
   {
      fs_log_error();
      close( new );
      free( ptr );
      return;
   }

   if ( ! SSL_set_fd( ptr->ssl, ptr->sock ))
   {
      fs_log_error();
      SSL_free( ptr->ssl );
      close( new );
      free( ptr );
      return;
   }

   ++fs_conn_count;

   fs_ev_set( ptr->sock, EVFILT_READ, EV_ADD | EV_ENABLE, ptr );
   fs_ev_set( ptr->sock, EVFILT_WRITE, EV_ADD | EV_DISABLE, ptr );

   if ( fs_accept_callback( ptr ))
   {
      --fs_conn_count;
      SSL_free( ptr->ssl );
      close( new );
      free( ptr );
      return;
   }
}

void fs_accept_connection()
{
   int conn;

   while(( conn = accept( fs_fd, NULL, NULL )) > 0 )
   {
      if ( fs_conn_count == fs_max_conn )
      {
         close( conn );
         return;
      }

      fs_add_conn( conn );
   }

   if ( conn < 0 && errno != ECONNABORTED && errno != EWOULDBLOCK && errno != EAGAIN )
   {
      syslog( LOG_ERR, "accept(): %m" );
      exit( 1 );
   }
}

void fs_set_events( struct fs_ccb *item, int i, int reading )
{
   int err = SSL_get_error( item->ssl, i );

   fs_clear_events( item );
   
   if ( err == SSL_ERROR_WANT_READ )
   {
      item->opposite = ! reading;
      fs_stop_write_socket( item );
      fs_start_read_socket( item );
      return;
   }

   if ( err == SSL_ERROR_WANT_WRITE )
   {
      item->opposite = reading;
      fs_stop_read_socket( item );
      fs_start_write_socket( item );
      return;
   }

   if ( err == SSL_ERROR_SSL || err == SSL_ERROR_SYSCALL )
   {
      fs_log_error();
      SSL_free( item->ssl );
      item->ssl = NULL;
   }

   fs_free_conn( item );
}

void fs_transfer_out( struct fs_ccb *item )
{
   int count;
   struct fs_queue *q;

   if ( item->first == NULL )
      return;

   if ( item->first->buffer != NULL && ( count = SSL_write( item->ssl, item->first->buffer, item->first->total )) <= 0 )
   {
      fs_set_events( item, count, 0 );
      return;
   }

   if ( item->first->sendfile )
   {
      ssize_t written;

      if ( item->first->buffer != NULL )
      {
         free( item->first->buffer );
         item->first->buffer = NULL;
      }

      if (( written = SSL_sendfile( item->ssl, item->first->sendfile, item->first->offset, item->first->len, 0 )) < 0 )
      {
         fs_set_events( item, written, 0 );
         return;
      }

      item->first->len    -= written;
      item->first->offset += written;
   }

   item->opposite = 0;

   fs_start_read_socket( item );
   fs_start_write_socket( item );

   if ( item->first->sendfile && item->first->len )
      return;

   q           = item->first;
   item->first = item->first->next;

   if ( q->buffer != NULL )
      free( q->buffer );

   free( q );
   --item->qlen;

   if ( item->first == NULL )
   {
      item->last = NULL;
      fs_stop_write_socket( item );
      fs_write_callback( item );
   }
}

void fs_transfer_in( struct fs_ccb *item )
{
   int i;

AGAIN:
   switch( item->stage )
   {
      /*
       * Read first byte of header.
       */

      case 0:

         if (( i = SSL_read( item->ssl, item->size, 1 )) <= 0 )
         {
            fs_set_events( item, i, 1 );
            return;
         }

         ++item->stage;

      /*
       * Read second byte of header.
       */

      case 1:

         if (( i = SSL_read( item->ssl, &item->size[ 1 ], 1 )) <= 0 )
         {
            fs_set_events( item, i, 1 );
            return;
         }

         item->total = item->len = ( item->size[ 0 ] << 8 ) + item->size[ 1 ];

         if ( item->total < 1 || item->total > MAX_FRAME )
         {
            fs_free_conn( item );
            return;
         }

         if ( item->buffer != NULL )
            free( item->buffer );

         if (( item->buffer = fs_memory( item->len )) == NULL )
         {
            fs_free_conn( item );
            return;
         }

         ++item->stage;
         item->offset = 0;

      /*
       * Read payload.
       */

      case 2:

         if (( i = SSL_read( item->ssl, &item->buffer[ item->offset ], item->total )) <= 0 )
         {
            fs_set_events( item, i, 1 );
            return;
         }

         item->opposite = 0;
         fs_start_read_socket( item );

         if ( item->qlen )
            fs_start_write_socket( item );
         else
            fs_stop_write_socket( item );

         item->offset += i;
         item->total  -= i;

         if ( item->total )
            goto NEXT;

         item->stage = 0;
         fs_read_callback( item, item->len, item->buffer );

      NEXT:
         if ( SSL_has_pending( item->ssl ))
            goto AGAIN;
   }
}

void fs_sigterm_handler( int signo )
{
   ++fs_sigterm;
}

/*
 * If we have dropped a connection, we traverse the output kqueue and mark
 * any events for that connection as invalid by setting the socket
 * descriptor to 0.
 */

void fs_set_sigterm_intr()
{
   struct sigaction sigact;

   sigact.sa_handler = fs_sigterm_handler;
   sigemptyset( &sigact.sa_mask );
   sigact.sa_flags = 0;

   if ( sigaction( SIGTERM, &sigact, NULL ) < 0 )
      syslog( LOG_ERR, "sigaction(): %m" );
}

void fs_process_clients()
{
   int kq;
   struct kevent *eptr;

   if (( kq = kqueue()) < 0 )
   {
      syslog( LOG_ERR, "kqueue(): %m" );
      exit( 1 );
   }

   fs_ev_set( fs_fd, EVFILT_READ, EV_ADD | EV_ENABLE, NULL );

   for( ; ; )
   {
      if ( fs_sigterm )
      {
         close( kq );
         break;
      }

      fs_set_sigterm_intr();
      fs_out = kevent( kq, fs_inqueue, fs_in, fs_outqueue, fs_qlen, NULL );
      fs_in = 0;

      if ( fs_out <= 0 )
      {
         if ( errno == EINTR )
            continue;

         syslog( LOG_ERR, "kevent(): %m" );
         exit( 1 );
      }

      signal( SIGTERM, fs_sigterm_handler );

      for( fs_idx = 0, eptr = &fs_outqueue[ fs_idx ]; fs_idx < fs_out; ++fs_idx, ++eptr )
      {
         struct fs_ccb *item;

         if ( fs_sigterm )
            break;

         if ( ! eptr->ident || eptr->flags & EV_ERROR )
            continue;

         item = ( struct fs_ccb *)eptr->udata;

         if ( eptr->filter == EVFILT_TIMER )
         {
            if ( fs_periodic != NULL )
               fs_periodic();

            continue;
         }

         if ( eptr->ident == fs_fd )
         {
            fs_accept_connection();
            continue;
         }

         if ( item->stage == -1  )
         {
            fs_ssl_accept( item );
            continue;
         }
         
         if ( eptr->filter == EVFILT_READ )
         {
            if ( item->opposite )
               fs_transfer_out( item );
            else
               fs_transfer_in( item );

            continue;
         }

         if ( item->opposite )
            fs_transfer_in( item );
         else
            fs_transfer_out( item );
      }
   }
}

int fs_password_callback( char *buf, int size, int rw, void *unused )
{
   char *ptr1, *ptr2;
   int len;

   for( len = 0, ptr1 = fs_password; *ptr1; ++ptr1 )
      ++len;

   if ( len + 1 > size )
   {
      *buf = '\0';
      syslog( LOG_ERR, "TLS password is too long for OpenSSL" );
      return 0;
   }

   ptr1 = fs_password;
   ptr2 = buf;

   while( *ptr1 )
      *ptr2++ = *ptr1++;

   *ptr2 = '\0';

   return len;
}

void fs_init_tls()
{
   static char context[ SSL_MAX_SSL_SESSION_ID_LENGTH ];
   int len;

   SSL_load_error_strings();
   SSL_library_init();

   long options = SSL_OP_NO_TLSv1_2 |
                  SSL_OP_NO_SSLv2 |
                  SSL_OP_NO_SSLv3 |
                  SSL_OP_NO_TLSv1 |
                  SSL_OP_NO_TLSv1_1 |
                  SSL_OP_NO_COMPRESSION |
                  SSL_OP_SINGLE_DH_USE |
                  SSL_OP_CIPHER_SERVER_PREFERENCE |
                  SSL_OP_IGNORE_UNEXPECTED_EOF |
                  SSL_OP_ENABLE_KTLS;

   if (( ctx = SSL_CTX_new( TLS_server_method() )) == NULL )
   {
      fs_log_error();
      exit( 1 );
   }

   if ( ! SSL_CTX_set_ecdh_auto( ctx, 1 ))
   {
      fs_log_error();
      exit( 1 );
   }

   if ( ! SSL_CTX_set_options( ctx, options ))
   {
      fs_log_error();
      exit( 1 );
   }

   if ( ! SSL_CTX_clear_options( ctx, SSL_OP_LEGACY_SERVER_CONNECT |
                                      SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION ))
   {
      fs_log_error();
      exit( 1 );
   }

   SSL_CTX_set_default_passwd_cb( ctx, fs_password_callback );

   if ( ! SSL_CTX_use_PrivateKey_file( ctx, fs_keyfile, SSL_FILETYPE_PEM ))
   {
      fs_log_error();
      exit( 1 );
   }

   if ( ! SSL_CTX_use_certificate_chain_file( ctx, fs_chainfile ))
   {
      fs_log_error();
      exit( 1 );
   }

   if ( ! SSL_CTX_check_private_key( ctx ))
   {
      fs_log_error();
      exit( 1 );
   }

   len = snprintf( context, sizeof( context ), "multifile-%u", getpid() );

   if ( ! SSL_CTX_set_session_id_context( ctx, ( const unsigned char *)context, len ))
   {
      fs_log_error();
      exit( 1 );
   }

   if ( ! SSL_CTX_set_session_cache_mode( ctx, SSL_SESS_CACHE_SERVER ))
   {
      fs_log_error();
      exit( 1 );
   }

   SSL_CTX_set_verify( ctx, SSL_VERIFY_NONE, NULL );
}

char *fs_read_line( int fd )
{
   char c, *ptr;
   static char buffer[ 1024 ];
   int r, count;

   count = 0;
   ptr = buffer;

   for( ; ; )
   {
      if (( r = read( fd, &c, 1 )) <= 0 )
      {
         if ( r < 0 )
            syslog( LOG_ERR, "read(): %m" );
         else if ( count )
            return buffer;

         return NULL;
      }

      if ( c == 10 )
         break;

      if ( c == 13 )
         continue;

      ++count;
      *ptr++ = c;
      *ptr = '\0';

      if ( count == sizeof( buffer ) - 1 )
         break;
   }

   return buffer;
}

void fs_read_tls_config()
{
   int fd;
   char *ptr;

   if (( fd = open( fs_tls_file, O_RDONLY )) < 0 )
   {
      syslog( LOG_ERR, "Could not open: %s", fs_tls_file );
      exit( 1 );
   }

   if (( ptr = fs_read_line( fd )) == NULL )
   {
      close( fd );
      syslog( LOG_ERR, "Could not read TLS keyfile from %s", fs_tls_file );
      exit( 1 );
   }

   fs_keyfile = fs_str_dup( ptr );

   if (( ptr = fs_read_line( fd )) == NULL )
   {
      close( fd );
      syslog( LOG_ERR, "Could not read TLS key password from %s", fs_tls_file );
      exit( 1 );
   }

   fs_password = fs_str_dup( ptr );

   if (( ptr = fs_read_line( fd )) == NULL )
   {
      close( fd );
      syslog( LOG_ERR, "Could not read TLS chainfile path from %s", fs_tls_file );
      exit( 1 );
   }

   fs_chainfile = fs_str_dup( ptr );
   close( fd );
}

void fs_set_signals()
{
   int *iptr;
   int sigs[] = {
      SIGPIPE, SIGHUP, SIGQUIT, SIGUSR1, SIGUSR2, SIGALRM, SIGINT, SIGTSTP, -1
   };

   signal( SIGTERM, fs_sigterm_handler );

   for( iptr = sigs; *iptr > 0; ++iptr )
      signal( *iptr, SIG_IGN );
}

void fs_init( int argc, char **argv )
{
   fs_set_signals();
   fs_set_options( argc, argv );
   fs_read_tls_config();
   fs_init_tls();

   fs_qlen = fs_max_conn * MAX_EVENT;

   if (( fs_inqueue = fs_memory( sizeof( struct kevent ) * fs_qlen )) == NULL )
      exit( 1 );

   bzero( fs_inqueue, sizeof( struct kevent ) * fs_qlen );

   if (( fs_outqueue = fs_memory( sizeof( struct kevent ) * fs_qlen )) == NULL )
      exit( 1 );

   bzero( fs_outqueue, sizeof( struct kevent ) * fs_qlen );
}

int main( int argc, char **argv )
{
   openlog( fs_app_name, LOG_PID, LOG_DAEMON );

   fs_init( argc, argv );
   fs_init_func();

   if ( chdir( fs_rootdir ) < 0 )
   {
      syslog( LOG_ERR, "chdir( %s ): %s", fs_rootdir, strerror( errno ));
      exit( 1 );
   }

   if ( chroot( fs_rootdir ) < 0 )
   {
      syslog( LOG_ERR, "chroot( %s ): %s", fs_rootdir, strerror( errno ));
      exit( 1 );
   }

   fs_start_listening();
   fs_change_identity();
   fs_process_clients();
   fs_exit_func();

   return 0;
}
