text: /**
   * @file PasswordListener.c
   * @author Ambroz Bizjak <ambrop7@gmail.com>
   * 
   * @section LICENSE
   * 
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   * 2. 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.
   * 3. Neither the name of the author nor the
   *    names of its contributors may be used to endorse or promote products
   *    derived from this software without specific prior written permission.
   * 
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 AUTHOR 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 <stdlib.h>
  
  #include <prerror.h>
  
  #include <ssl.h>
  
  #include <misc/offset.h>
  #include <misc/byteorder.h>
  #include <misc/balloc.h>
  #include <misc/compare.h>
  #include <base/BLog.h>
  #include <security/BRandom.h>
  #include <nspr_support/DummyPRFileDesc.h>
  
  #include <client/PasswordListener.h>
  
  #include <generated/blog_channel_PasswordListener.h>
  
  static int password_comparator (void *user, uint64_t *p1, uint64_t *p2);
  static void remove_client (struct PasswordListenerClient *client);
  static void listener_handler (PasswordListener *l);
  static void client_connection_handler (struct PasswordListenerClient *client, int event);
  static void client_sslcon_handler (struct PasswordListenerClient *client, int event);
  static void client_receiver_handler (struct PasswordListenerClient *client);
  
  int password_comparator (void *user, uint64_t *p1, uint64_t *p2)
  {
      return B_COMPARE(*p1, *p2);
  }
  
  void remove_client (struct PasswordListenerClient *client)
  {
      PasswordListener *l = client->l;
      
      // stop using any buffers before they get freed
      if (l->ssl) {
          BSSLConnection_ReleaseBuffers(&client->sslcon);
      }
      
      // free receiver
      SingleStreamReceiver_Free(&client->receiver);
      
      // free SSL
      if (l->ssl) {
          BSSLConnection_Free(&client->sslcon);
          ASSERT_FORCE(PR_Close(client->sock->ssl_prfd) == PR_SUCCESS)
      }
      
      // free connection interfaces
      BConnection_RecvAsync_Free(&client->sock->con);
      BConnection_SendAsync_Free(&client->sock->con);
      
      // free connection
      BConnection_Free(&client->sock->con);
      
      // free sslsocket structure
      free(client->sock);
      
      // move to free list
      LinkedList1_Remove(&l->clients_used, &client->list_node);
      LinkedList1_Append(&l->clients_free, &client->list_node);
  }
  
  void listener_handler (PasswordListener *l)
  {
      DebugObject_Access(&l->d_obj);
      
      // obtain client entry
      if (LinkedList1_IsEmpty(&l->clients_free)) {
          struct PasswordListenerClient *client = UPPER_OBJECT(LinkedList1_GetFirst(&l->clients_used), struct PasswordListenerClient, list_node);
          remove_client(client);
      }
      struct PasswordListenerClient *client = UPPER_OBJECT(LinkedList1_GetLast(&l->clients_free), struct PasswordListenerClient, list_node);
      LinkedList1_Remove(&l->clients_free, &client->list_node);
      LinkedList1_Append(&l->clients_used, &client->list_node);
      
      // allocate sslsocket structure
      if (!(client->sock = (sslsocket *)malloc(sizeof(*client->sock)))) {
          BLog(BLOG_ERROR, "malloc failedt");
          goto fail0;
      }
      
      // accept connection
      if (!BConnection_Init(&client->sock->con, BConnection_source_listener(&l->listener, NULL), l->bsys, client, (BConnection_handler)client_connection_handler)) {
          BLog(BLOG_ERROR, "BConnection_Init failed");
          goto fail1;
      }
      
      BLog(BLOG_INFO, "Connection accepted");
      
      // init connection interfaces
      BConnection_SendAsync_Init(&client->sock->con);
      BConnection_RecvAsync_Init(&client->sock->con);
      
      StreamPassInterface *send_if = BConnection_SendAsync_GetIf(&client->sock->con);
      StreamRecvInterface *recv_if = BConnection_RecvAsync_GetIf(&client->sock->con);
      
      if (l->ssl) {
          // create bottom NSPR file descriptor
          if (!BSSLConnection_MakeBackend(&client->sock->bottom_prfd, send_if, recv_if, l->twd, l->ssl_flags)) {
              BLog(BLOG_ERROR, "BSSLConnection_MakeBackend failed");
              goto fail2;
          }
          
          // create SSL file descriptor from the bottom NSPR file descriptor
          if (!(client->sock->ssl_prfd = SSL_ImportFD(l->model_prfd, &client->sock->bottom_prfd))) {
              ASSERT_FORCE(PR_Close(&client->sock->bottom_prfd) == PR_SUCCESS)
              goto fail2;
          }
          
          // set server mode
          if (SSL_ResetHandshake(client->sock->ssl_prfd, PR_TRUE) != SECSuccess) {
              BLog(BLOG_ERROR, "SSL_ResetHandshake failed");
              goto fail3;
          }
          
          // set require client certificate
          if (SSL_OptionSet(client->sock->ssl_prfd, SSL_REQUEST_CERTIFICATE, PR_TRUE) != SECSuccess) {
              BLog(BLOG_ERROR, "SSL_OptionSet(SSL_REQUEST_CERTIFICATE) failed");
              goto fail3;
          }
          if (SSL_OptionSet(client->sock->ssl_prfd, SSL_REQUIRE_CERTIFICATE, PR_TRUE) != SECSuccess) {
              BLog(BLOG_ERROR, "SSL_OptionSet(SSL_REQUIRE_CERTIFICATE) failed");
              goto fail3;
          }
          
          // initialize SSLConnection
          BSSLConnection_Init(&client->sslcon, client->sock->ssl_prfd, 0, BReactor_PendingGroup(l->bsys), client, (BSSLConnection_handler)client_sslcon_handler);
          
          send_if = BSSLConnection_GetSendIf(&client->sslcon);
          recv_if = BSSLConnection_GetRecvIf(&client->sslcon);
      }
      
      // init receiver
      SingleStreamReceiver_Init(&client->receiver, (uint8_t *)&client->recv_buffer, sizeof(client->recv_buffer), recv_if, BReactor_PendingGroup(l->bsys), client, (SingleStreamReceiver_handler)client_receiver_handler);
      
      return;
      
      // cleanup on error
  fail3:
      if (l->ssl) {
          ASSERT_FORCE(PR_Close(client->sock->ssl_prfd) == PR_SUCCESS)
      }
  fail2:
      BConnection_RecvAsync_Free(&client->sock->con);
      BConnection_SendAsync_Free(&client->sock->con);
      BConnection_Free(&client->sock->con);
  fail1:
      free(client->sock);
  fail0:
      LinkedList1_Remove(&l->clients_used, &client->list_node);
      LinkedList1_Append(&l->clients_free, &client->list_node);
  }
  
  void client_connection_handler (struct PasswordListenerClient *client, int event)
  {
      PasswordListener *l = client->l;
      DebugObject_Access(&l->d_obj);
      
      if (event == BCONNECTION_EVENT_RECVCLOSED) {
          BLog(BLOG_INFO, "connection closed");
      } else {
          BLog(BLOG_INFO, "connection error");
      }
      
      remove_client(client);
  }
  
  void client_sslcon_handler (struct PasswordListenerClient *client, int event)
  {
      PasswordListener *l = client->l;
      DebugObject_Access(&l->d_obj);
      ASSERT(l->ssl)
      ASSERT(event == BSSLCONNECTION_EVENT_ERROR)
      
      BLog(BLOG_INFO, "SSL error");
      
      remove_client(client);
  }
  
  void client_receiver_handler (struct PasswordListenerClient *client)
  {
      PasswordListener *l = client->l;
      DebugObject_Access(&l->d_obj);
      
      // check password
      uint64_t received_pass = ltoh64(client->recv_buffer);
      BAVLNode *pw_tree_node = BAVL_LookupExact(&l->passwords, &received_pass);
      if (!pw_tree_node) {
          BLog(BLOG_WARNING, "unknown password");
          remove_client(client);
          return;
      }
      PasswordListener_pwentry *pw_entry = UPPER_OBJECT(pw_tree_node, PasswordListener_pwentry, tree_node);
      
      BLog(BLOG_INFO, "Password recognized");
      
      // remove password entry
      BAVL_Remove(&l->passwords, &pw_entry->tree_node);
      
      // stop using any buffers before they get freed
      if (l->ssl) {
          BSSLConnection_ReleaseBuffers(&client->sslcon);
      }
      
      // free receiver
      SingleStreamReceiver_Free(&client->receiver);
      
      if (l->ssl) {
          // free SSL connection
          BSSLConnection_Free(&client->sslcon);
      } else {
          // free connection interfaces
          BConnection_RecvAsync_Free(&client->sock->con);
          BConnection_SendAsync_Free(&client->sock->con);
      }
      
      // remove connection handler
      BConnection_SetHandlers(&client->sock->con, NULL, NULL);
      
      // move client entry to free list
      LinkedList1_Remove(&l->clients_used, &client->list_node);
      LinkedList1_Append(&l->clients_free, &client->list_node);
      
      // give the socket to the handler
      pw_entry->handler_client(pw_entry->user, client->sock);
      return;
  }
  
  int PasswordListener_Init (PasswordListener *l, BReactor *bsys, BThreadWorkDispatcher *twd, BAddr listen_addr, int max_clients, int ssl, int ssl_flags, CERTCertificate *cert, SECKEYPrivateKey *key)
  {
      ASSERT(BConnection_AddressSupported(listen_addr))
      ASSERT(max_clients > 0)
      ASSERT(ssl == 0 || ssl == 1)
      
      // init arguments
      l->bsys = bsys;
      l->twd = twd;
      l->ssl = ssl;
      l->ssl_flags = ssl_flags;
      
      // allocate client entries
      if (!(l->clients_data = (struct PasswordListenerClient *)BAllocArray(max_clients, sizeof(struct PasswordListenerClient)))) {
          BLog(BLOG_ERROR, "BAllocArray failed");
          goto fail0;
      }
      
      if (l->ssl) {
          // initialize model SSL fd
          DummyPRFileDesc_Create(&l->model_dprfd);
          if (!(l->model_prfd = SSL_ImportFD(NULL, &l->model_dprfd))) {
              BLog(BLOG_ERROR, "SSL_ImportFD failed");
              ASSERT_FORCE(PR_Close(&l->model_dprfd) == PR_SUCCESS)
              goto fail1;
          }
          
          // set server certificate
          if (SSL_ConfigSecureServer(l->model_prfd, cert, key, NSS_FindCertKEAType(cert)) != SECSuccess) {
              BLog(BLOG_ERROR, "SSL_ConfigSecureServer failed");
              goto fail2;
          }
      }
      
      // initialize client entries
      LinkedList1_Init(&l->clients_free);
      LinkedList1_Init(&l->clients_used);
      for (int i = 0; i < max_clients; i++) {
          struct PasswordListenerClient *conn = &l->clients_data[i];
          conn->l = l;
          LinkedList1_Append(&l->clients_free, &conn->list_node);
      }
      
      // initialize passwords tree
      BAVL_Init(&l->passwords, OFFSET_DIFF(PasswordListener_pwentry, password, tree_node), (BAVL_comparator)password_comparator, NULL);
      
      // initialize listener
      if (!BListener_Init(&l->listener, listen_addr,  l->bsys, l, (BListener_handler)listener_handler)) {
          BLog(BLOG_ERROR, "Listener_Init failed");
          goto fail2;
      }
      
      DebugObject_Init(&l->d_obj);
      return 1;
      
      // cleanup
  fail2:
      if (l->ssl) {
          ASSERT_FORCE(PR_Close(l->model_prfd) == PR_SUCCESS)
      }
  fail1:
      BFree(l->clients_data);
  fail0:
      return 0;
  }
  
  void PasswordListener_Free (PasswordListener *l)
  {
      DebugObject_Free(&l->d_obj);
  
      // free clients
      LinkedList1Node *node;
      while (node = LinkedList1_GetFirst(&l->clients_used)) {
          struct PasswordListenerClient *client = UPPER_OBJECT(node, struct PasswordListenerClient, list_node);
          remove_client(client);
      }
      
      // free listener
      BListener_Free(&l->listener);
      
      // free model SSL file descriptor
      if (l->ssl) {
          ASSERT_FORCE(PR_Close(l->model_prfd) == PR_SUCCESS)
      }
      
      // free client entries
      BFree(l->clients_data);
  }
  
  uint64_t PasswordListener_AddEntry (PasswordListener *l, PasswordListener_pwentry *entry, PasswordListener_handler_client handler_client, void *user)
  {
      DebugObject_Access(&l->d_obj);
      
      while (1) {
          // generate password
          BRandom_randomize((uint8_t *)&entry->password, sizeof(entry->password));
          
          // try inserting
          if (BAVL_Insert(&l->passwords, &entry->tree_node, NULL)) {
              break;
          }
      }
      
      entry->handler_client = handler_client;
      entry->user = user;
      
      return entry->password;
  }
  
  void PasswordListener_RemoveEntry (PasswordListener *l, PasswordListener_pwentry *entry)
  {
      DebugObject_Access(&l->d_obj);
      
      // remove
      BAVL_Remove(&l->passwords, &entry->tree_node);
  }
  