/*
 * Secure X Message Passing Library v2 examples.
 *
 * (c) Alexander Vdolainen 2013-2015 <avdolainen@gmail.com>
 * (c) Alexander Vdolainen 2016 <avdolainen@zoho.com>
 *
 * libsxmp is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * libsxmp is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.";
 *
 */

/*
 * This is an example of sxmp usage.
 * NOTE: It was developed quite fast within one day,
 * btw - this the reason of some ugly code here.
 *
 * This is a master or simply - daemon part, it will
 * listen a requests and create a stream with directory entries,
 * which ridden by the client.
 * Using the builtin stream functionality added since 0.4.2 version.
 *
 * This implements a simple client-server topology, to see
 * more advanced technics check out other examples.
 *
 * NOTE(win32): don't have a time to test it or fix it to
 * make it works on windows, if you can - u're welcome.
 */

#include <stdio.h>
#include <dirent.h>
#define __USE_GNU
#include <stdlib.h>
#include <stdarg.h>
#include <sys/types.h>
#include <limits.h>
#include <sys/select.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/resource.h>
#include <netdb.h>
#include <unistd.h>
#include <uuid/uuid.h>
#include <execinfo.h>
#include <netinet/in.h>

#include <tdata/usrtc.h>
#include <tdata/list.h>
#include <sexpr/sexp.h>
#include <sxmp/limits.h>
#include <sxmp/sxmp.h>

#include <pthread.h>
#include <stdint.h>
#include <getopt.h>
#include <errno.h>

#include "filelist.h"
#include "../config.h"

#define FULL_PROGRAM_NAME        "File lister master (builtin streams based)"
#include "helpers.h"

/* firstly we need to take an rpc calls data,
 * sxmp uses usrtc from libtdata to store it
 */
static usrtc_t *fulist;

/* ok, here we will define out sxmp specific functions */

/*
 * RPC list contain groups of RPC calls called channel types
 * for each sxmp link depends on access rules you can change
 * available channel types.
 * To do so you must provide a special callback returning this list.
 * Since we haven't any access restrictions in our example, just
 * create a fake.
 */
static usrtc_t *__rettlist(sxlink_t *l)
{
  return fulist;
}

/*
 * Each sxmp link validated by certificate ID,
 * i.e. when basic checking is done, you can also
 * review the certificate ID, for example you need
 * to disable certificate for a time being.
 *
 * Here we don't have those checks, just always accept
 */
static int __validate_sslpem(sxlink_t *l)
{
  return 0;
}

/*
 * To be more paranoic, sxmp also allow to
 * check up username and password.
 * We don't overview this tuff here, just
 * always allow.
 */
static int __secure_check(sxlink_t *l)
{
  return SXE_SUCCESS;
}

/* misc functions */
static int __ping(void *m, sexp_t *sx)
{
  return sxmsg_return((sxmsg_t*)m, SXE_SUCCESS);
}

/* dir listing stream */
typedef struct __dirlist_type {
  DIR *dp;
  struct dirent dent;
  struct dirent *dres;
} dirlist_t;

static int dirlist_open(sxlink_t *link, struct sxstream_opened *stream,
                        const char *dirname)
{
  dirlist_t *pslist = malloc(sizeof(dirlist_t));

  if(!pslist) return SXE_ENOMEM;
  else memset(pslist, 0, sizeof(dirlist_t));

  if(!(pslist->dp = opendir(dirname ? dirname : "./"))) {
    free(pslist);
    return SXE_FAILED;
  } else sxstream_opened_setpriv(stream, pslist);

  return SXE_SUCCESS;
}

static int dirlist_close(struct sxstream_opened *stream)
{
  dirlist_t *pslist = sxstream_opened_getpriv(stream);
  list_head_t *llist = sxstream_opened_getelist(stream);

  if(!pslist) return SXE_FAILED;
  if(llist) sxstream_generic_slist_free(llist);

  /* close dir and free strcuture */
  if(pslist->dp) closedir(pslist->dp);
  free(pslist);

  return SXE_SUCCESS;
}

static inline const char *dump_type(struct dirent *d)
{
  if(!d) return "nil";

  switch(d->d_type) {
  case DT_REG:    return "regular";
  case DT_DIR:    return "directory";
  case DT_BLK:    return "block";
  case DT_CHR:    return "char";
  case DT_FIFO:    return "fifo";
  case DT_LNK:    return "link";
  case DT_UNKNOWN:    return "unknown";
  default:    return "another";
  }

  return "nil";
}

static size_t dirlist_read(struct sxstream_opened *stream, size_t sz, uint64_t off,
                           list_head_t **rlist)
{
  dirlist_t *pslist = sxstream_opened_getpriv(stream);
  list_head_t *llist_prev = sxstream_opened_getelist(stream);
  list_head_t *llist_new = NULL;

  if(!pslist) return -1;

  /* read and fill new list */
  if(readdir_r(pslist->dp, &(pslist->dent), &(pslist->dres))) return -1;

  /* if finished return 0 */
  if(!pslist->dres) {
    if(llist_prev) sxstream_generic_slist_free(llist_prev);
    stream->ent_buf = NULL;
    return 0;
  }
  /* allocate a new */
  if(!(llist_new = sxstream_generic_slist_alloc())) return -1;
  else { /* fill this list */
    if(sxstream_generic_slist_additem(llist_new, pslist->dres->d_name)) {
    __fail:
      sxstream_generic_slist_free(llist_new);
      return -1;
    }
    if(sxstream_generic_slist_additem(llist_new, dump_type(pslist->dres)))
      goto __fail;
  }
  /* otherwise destroy last list */
  if(llist_prev) sxstream_generic_slist_free(llist_prev);
  *rlist = llist_new; /* assign new list */

  return 1;
}

static struct sxstream_ops dirlist_ops = {
  .s_open = dirlist_open,
  .s_close = dirlist_close,
  .s_eread = dirlist_read,
  .s_ewrite = NULL,
};

static struct sxstream_description dirlist_stream = {
  .stid = DIRLIST_ID,
  .pcid = READONLY_CHANNEL,
  .type = 0, /* just a simple one */
  .flags = SXE_O_READ | SXE_O_TRUNC,
  .ops = &dirlist_ops,
};

/* command line options helpers */
static void __help_print(FILE *fso, const char *fmtname)
{
  fprintf(fso, "\n%s\n\n", FULL_PROGRAM_NAME);

  /* usage options */
  fprintf(fso, "Usage:\n");
  fprintf(fso, "\t%s -r | --root-x509 <path> -u | --master-x509 <path> "
          " [-p | --listen-port <port>]  [-d | --daemonize]\n", fmtname);

  /* defaults */
  fprintf(fso, "\t%s -h | --help\n", fmtname);
  fprintf(fso, "\t%s -v | --version\n", fmtname);

  /* options description */
  fprintf(fso, "\nOptions:\n");
  fprintf(fso, "\t%-33s Set pathname of the remote root X.509 public certificate.\n",
          "-r | --root-x509 <path>");
  fprintf(fso, "\t%-33s Set pathname of the host X.509 public and private certificate.\n",
          "-m | --master-x509 <path>");
  fprintf(fso, "\t%-33s Remote host listening port number [default: %d].\n",
          "-p | --listen-port <port>", DEFAULT_PORT);
  fprintf(fso, "\t%-33s Work as a daemon.\n", "-d | --daemonize");
  fprintf(fso, "\t%-33s Show help screen.\n", "-h | --help");
  fprintf(fso, "\t%-33s Print version information.\n", "-v | --version");
  return;
}

int main(int argc, char **argv)
{
  char *rootca = NULL, *cert = NULL;
  sxhub_t *sxmphub = sxhub_create();
  int port = DEFAULT_PORT;
  int opt;
  int is_daemon = 0;

  while(1) {
    int option_index = 0;
    static struct option long_options[] = {
      /* These options a generic ones. */
      {"help", no_argument, NULL, 'h'}, /* print out help and version info */
      {"version", no_argument, NULL, 'v'}, /* just out a version info */
      /*  The following options about settings . */
      {"root-x509", required_argument, NULL, 'r'},
      {"master-x509", required_argument, NULL, 'm'},
      {"listen-port", required_argument, NULL, 'p'},
      {"daemonize", required_argument, NULL, 'd'},
      { NULL, 0, NULL, 0 },
    };

    if((opt = getopt_long(argc, argv, "dhvr:m:p:", long_options,
                          &option_index)) == -1) break;

    switch(opt) {
    case 'h':
      __help_print(stdout, argv[0]);
      return 0;
      break;
    case 'v':
      version_print(stdout);
      return 0;
      break;
    case 'r':
      if(!(rootca = strdup(optarg))) return ENOMEM;
      break;
    case 'm':
      if(!(cert = strdup(optarg))) return ENOMEM;
      break;
    case 'p':
      port = atoi(optarg);
      break;
    case 'd':
      is_daemon = 1;
      break;
    }
  }

  if(!rootca) {
    fprintf(stderr, "Root CA not pointed.\n Failure.\n");
    __help_print(stderr, argv[0]);
    return EINVAL;
  }

  if(!cert) {
    fprintf(stderr, "User certificate not pointed.\n Failure.\n");
    __help_print(stderr, argv[0]);
    return EINVAL;
  }

  /* become daemon if it's necessary */
  if (is_daemon && (opt = daemonize())) {
    fprintf(stderr, "daemonize() failed (%d).\n Failure.\n", opt);
    return EINVAL;
  }

  sxmp_init();

  /* all is fine let's init connection subsystem */
  if(!sxmphub) {
    fprintf(stderr, "Subsystem init failed: %d\n", opt);
    return 2;
  }
  /* set working certificates */
  opt = sxhub_setsslserts(sxmphub, rootca, cert, cert);
  if(opt) {
    fprintf(stderr, "Subsystem init failed (set SSL x.509 pems): %d\n", opt);
    return opt;
  }

  /* clean up */
  free(rootca);
  free(cert);

  /* set important callbacks to do the security checking */
  sxhub_set_authcheck(sxmphub, __secure_check);
  sxhub_set_sslvalidate(sxmphub, __validate_sslpem);

  /* ok, now we need to construct RPC lists (channels) */
  if(!(fulist = malloc(sizeof(usrtc_t)))) {
    fprintf(stderr, "Cannot allocate memory for RPC lists\n Failure.\n");
    return ENOMEM;
  }
  opt = sxmp_rpclist_init(fulist);
  if(opt) {
    fprintf(stderr, "Failed to init rpc list\n Failure.\n");
    return opt;
  }

  /* we will add one channel with type id READONLY_CHANNEL for read only operations */
  opt = sxmp_rpclist_add(fulist, READONLY_CHANNEL, "Read only operations", NULL);
  if(opt) {
    fprintf(stderr, "Failed to add typed RPC channel\n Failure.\n");
    return opt;
  }

  /* ok, let's add stream functions */
  opt = sxmp_rpclist_add_function(fulist, READONLY_CHANNEL, "ping", __ping);
  if(opt) {
    fprintf(stderr, "Failed to add functions to typed RPC channel\n Failure.\n");
    return opt;
  }

  /* ok, setup it */
  sxhub_set_rpcvalidator(sxmphub, __rettlist);

  /* setup our stream */
  sxhub_stream_register(sxmphub, &dirlist_stream);

  signal(SIGPIPE, SIG_IGN);

  /* now we're ready to run the listen process */
  int srv = openlistener_socket(port);

  while(1) {
    struct sockaddr_in addr;
    socklen_t len = sizeof(addr);
    sxlink_t *link;

    int client = accept(srv, (struct sockaddr*)&addr, &len); /* accept connection as usual */

    link = sxlink_master_accept(sxmphub, client, NULL);

    if(!link) fprintf(stderr, "Cannot create sxmp link (%d)\n", errno);
  }

  sxhub_destroy(sxmphub);

  sxmp_finalize();

  return 0;
}