/*
 * ejabberd external authentication program
 *
 * (c) Alexander Vdolainen 2013, 2018, 2019, 2021 <alex@vapaa.xyz>
 *
 * this is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published
 * by the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * this 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 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/>.";
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/select.h>
#include <errno.h>
#include <netdb.h>
#include <string.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <stdarg.h>
#include <getopt.h>

#include <ejabbermsg.h>
#include <tlsport.h>
#include <imapf.h>
#include <smtpf.h>
#include <eport.h>

unsigned long djb_hash(const char *s)
{
  unsigned long hash = 5381;
  int c;

  while((c = *s++))
    hash = ((hash << 5) + hash) + c;

  return hash;
}

#define MAX_SUPPL_DATA  64

#define PRG_NAME  "daemon for ejabberd to provide IMAPS/SMTPS auth"

static void print_help(const char *cname)
{
  fprintf(stdout, "\n%s - %s\n\n", cname, PRG_NAME);
  fprintf(stdout, "USAGE:\n");
  fprintf(stdout, "\t%s -S | --smtp-host <SMTP hostname> -s | --smtp-port <port> "
          "-I | --imap-host <IMAP hostname> -i | --imap-port <port>", cname);
  fprintf(stdout, "\n\n\t%s -h | --help\n", cname);
  fprintf(stdout, "\nNOTE: This program is designed to be used from ejabberd only.\n");

  return;
}

int main(int argc, char **argv)
{
  char *smtp_host, *smtp_port, *imap_host, *imap_port;
  char msgbuf[MAX_USER_LEN + MAX_DOMAIN_LEN + MAX_PASSWORD_LEN + MAX_SUPPL_DATA];
  struct tlsport connection;
  struct ejabber_msg m;
  int opt, msgbuflen = 0;

  smtp_host = smtp_port = imap_port = imap_host = NULL;

  while(1) {
    int option_index = 0;
    static struct option long_options[] = {
      {"help", no_argument, NULL, 'h'},
      {"smtp-host", required_argument, NULL, 'S'},
      {"smtp-port", required_argument, NULL, 's'},
      {"imap-host", required_argument, NULL, 'I'},
      {"imap-port", required_argument, NULL, 'i'},
      {NULL, 0, NULL, 0},
    };

    if((opt = getopt_long(argc, argv, "hS:s:I:i:", long_options, &option_index)) == -1)
      break;

    switch(opt) {
    case 'h':
      print_help(argv[0]);
      return 0;
      break;
    case 'S': smtp_host = optarg; break;
    case 's': smtp_port = optarg; break;
    case 'I': imap_host = optarg; break;
    case 'i': imap_port = optarg; break;
    default: abort(); break;
    }
  }

  /* arguments check up */
  if(!smtp_host || !smtp_port) {
    fprintf(stderr, "Error: SMTPs information is not complete.\n");
  main_einval_moimoi:
    print_help(argv[0]);
    return EINVAL;
  }
  if(!imap_port || !imap_host) {
    fprintf(stderr, "Error: IMAPs information is not complete.\n");
    goto main_einval_moimoi;
  }

  /* take care about space borrowed from stack */
  msgbuflen = MAX_USER_LEN + MAX_DOMAIN_LEN + MAX_PASSWORD_LEN + MAX_SUPPL_DATA;
  memset(msgbuf, 0, msgbuflen);
  /* libs init */
  ssllib_init();

  /* main loop */
  while(eport_read(stdin, msgbuf, msgbuflen) > 0) {
    opt = eport_ejabberd_msgread(msgbuf, msgbuflen, &m);
    if(opt > 0) {
    abort_parsing:
      fprintf(stderr, "Error: message isn't valid.\nExiting.\n");
      ssllib_free();
      abort();
    }

    /* deal with message */
    switch(ejabber_msg_event(&m)) {
    case EJA_AUTH:
      if(opt != 3) goto abort_parsing;

      /* connect to IMAPs */
      if(tls_connect(imap_host, imap_port, &connection)) {
        fprintf(stderr, "Error: unable to connect to IMAPs.\n");
        opt = -1;
      } else {
        opt = imap_auth(&connection, &m);
        tls_close(&connection);
      }
      break;
    case EJA_ISUSER:
      if(opt != 2) goto abort_parsing;

      /* connect to SMTPs */
      if(tls_connect(smtp_host, smtp_port, &connection)) {
        fprintf(stderr, "Error: unable to connect to SMTPs.\n");
        opt = -1;
      } else {
        opt = smtp_checkuser(&connection, &m, smtp_host);
        tls_close(&connection);
      }
      break;
    case EJA_SETPASS:
    case EJA_TRYREGISTER:
    case EJA_REMOVEUSER:
    case EJA_REMOVEUSER3:
      /* still aren't supported by the program */
      fprintf(stderr, "Error: This type of message are not supported.\n");
      opt = -1;
      break;
    case EJA_UNKNOWN:
    default: /* something goes wrong with message */
      goto abort_parsing;
      break;
    }

    /* finally, reply ... */
    eport_write(stdout, (char *)((int16_t *)&opt), sizeof(int16_t));
  }

  ssllib_free();

  return 0;
}