/* This file is part of the Project Athena Zephyr Notification System.
 * It is one of the source files comprising zwgc, the Zephyr WindowGram
 * client.
 *
 *      Created by:     Marc Horowitz <marc@athena.mit.edu>
 *
 *      $Id: 79862420439cdfc1798273b9fd2e22ef9979bc19 $
 *
 *      Copyright (c) 1989 by the Massachusetts Institute of Technology.
 *      For copying and distribution information, see the file
 *      "mit-copyright.h".
 */

#include <sysdep.h>
#ifdef HAVE_ARES
#include <ares.h>
#endif

#if (!defined(lint) && !defined(SABER))
static const char rcsid_main_c[] = "$Id: 79862420439cdfc1798273b9fd2e22ef9979bc19 $";
#endif

#include <netdb.h>
#include <arpa/nameser.h>
#ifdef HAVE_ARPA_NAMESER_COMPAT_H
#include <arpa/nameser_compat.h>
#endif
#include <sys/socket.h>
#include <sys/resource.h>
#include <locale.h>
#include <zephyr/mit-copyright.h>
#include <zephyr/zephyr.h>

#include "new_memory.h"
#include "zwgc.h"
#include "parser.h"
#include "node.h"
#include "exec.h"
#include "zephyr.h"
#include "notice.h"
#include "subscriptions.h"
#include "file.h"
#include "mux.h"
#include "port.h"
#include "variables.h"
#include "main.h"
#ifdef CMU_ZCTL_PUNT
#include "int_dictionary.h"
#endif
#ifdef CMU_ZWGCPLUS
#include "plus.h"
int zwgcplus = 1;
#endif

void notice_handler(ZNotice_t *);
static void process_notice(ZNotice_t *, char *);
static void setup_signals(int);
static void detach(void);
static void signal_exit(int);
#ifdef HAVE_ARES
static void notice_callback(void *, int, int, char *, char *);
#endif

/*
 * Global zwgc-wide variables:
 */

#ifdef DEBUG
int zwgc_debug = 0;
#endif

static char *zwgc_version_string = "1.0";

/*
 * description_filename_override - <<<>>>
 */

static char *description_filename_override = NULL;

/*
 * progname - <<<>>> export!
 */

char *progname = NULL;

/*
 * subscriptions_filename_override - <<<>>> export!
 */

char *subscriptions_filename_override = NULL;

/*
 * location_override - <<<>>> export!
 */

char *location_override = NULL;

#ifdef HAVE_ARES
/*
 * achannel - <<<>>> export!
 */

ares_channel achannel;
#endif

/****************************************************************************/
/*                                                                          */
/*             Code to deal with reading in the description file:           */
/*                                                                          */
/****************************************************************************/

/*
 * program - this holds a pointer to the node representation of the
 *           description file once it has been read in.
 */

static struct _Node *program = NULL;

/*
 * <<<>>>
 */

static void
fake_startup_packet(void)
{
    ZNotice_t *notice = (ZNotice_t *)malloc(sizeof(ZNotice_t));
    struct timezone tz;
    char msgbuf[BUFSIZ];
    extern void Z_gettimeofday(struct _ZTimeval *, struct timezone *);

    var_set_variable("version", zwgc_version_string);

    (void) memset(notice, 0, sizeof(ZNotice_t));

    notice->z_version = "";
    notice->z_class = "WG_CTL_CLASS";
    notice->z_class_inst = "WG_CTL_USER<<<>>>";
    notice->z_opcode = "WG_STARTUP";
    notice->z_default_format = "Zwgc mark II version $version now running...\n";
    notice->z_recipient = "";
    notice->z_sender = "ZWGC";
    Z_gettimeofday(&notice->z_time, &tz);
    notice->z_port = 0;
    notice->z_kind = ACKED;
    notice->z_auth = ZAUTH_YES;
    notice->z_charset = ZCHARSET_UNKNOWN;
    sprintf(msgbuf,"Zwgc mark II version %s now running...",
            zwgc_version_string);
    notice->z_message = msgbuf;
    notice->z_message_len = strlen(notice->z_message)+1;

#ifdef CMU_ZWGCPLUS
    list_add_notice(notice);
    set_notice_fake(notice, 1);
#endif
    process_notice(notice, NULL);
#ifdef CMU_ZWGCPLUS
    list_del_notice(notice);
#else
    free(notice);
#endif
}

static void
read_in_description_file(void)
{
    FILE *input_file;
    char defdesc[128];

/*    var_clear_all_variables(); <<<>>> */

    sprintf(defdesc, "%s/zephyr/%s", DATADIR, DEFDESC);
    input_file = locate_file(description_filename_override, USRDESC, defdesc);
    if (input_file)
      program = parse_file(input_file);
    else
      program = NULL;

    fake_startup_packet();
}

/****************************************************************************/
/*                                                                          */
/*            Code to deal with argument parsing & overall control:         */
/*                                                                          */
/****************************************************************************/

/*
 *    void usage()
 *        Effects: Prints out an usage message on stderr then exits the
 *                 program with error code 1.
 */

void
usage(void)
{
#ifdef DEBUG
    fprintf(stderr, "\
zwgc: usage: zwgc [-debug] [-f <filename>] [-subfile <filename>]\n\
                  [-ttymode] [-nofork] [-reenter] [-loc text]\n\
                  [-default <driver>] {-disable <driver>}*\n\
                  [output driver options]\n");
#else
    fprintf(stderr, "\
zwgc: usage: zwgc [-f <filename>] [-subfile <filename>]\n\
                  [-ttymode] [-nofork] [-reenter] [-loc text]\n\
                  [-default <driver>] {-disable <driver>}*\n\
                  [output driver options]\n");
#endif
    exit(1);
}

/*
 * <<<>>>
 */

static void
run_initprogs(void)
{
    /*
     * This code stolen from old zwgc: yuck.  Clean up & fix.  <<<>>>
     * Should this fork instead of just systeming?
     */

    int status;
    char *iprogname = ZGetVariable("initprogs");

    if (!iprogname)
      return;

    status = system(iprogname);
    if (status == 127) {
        perror("zwgc initprog exec");
        fprintf(stderr,"zwgc initprog of <%s> failed: no shell.\n",
                iprogname);
    } else if (status!=-1 && status>>8) {
        perror("zwgc initprog exec");
        fprintf(stderr,"zwgc initprog of <%s> failed with status [%d].\n",
                iprogname, status>>8);
    }
}

/*
 * main -- the program entry point.  Does parsing & top level control.
 */

int
main(int argc, char **argv)
{
    char **new;
    register char **current;
    int dofork = 1;
#ifdef HAVE_ARES
    int status;
#endif

    setlocale(LC_ALL, "");
    progname = argv[0];

    /*
     * Process "-f <filename>", "-subfile <filename>", "-nofork",
     * "-reenter" (which is ignored) and (if DEBUG) "-debug"
     * arguments, removing then from argc, argv:
     */
    for (new=current=argv+1; *current; current++) {
        if (string_Eq(*current, "-debug")) {
            argc--;
#ifdef DEBUG
            zwgc_debug = 1;
#endif
        } else if (string_Eq(*current, "-f")) {
            argc -= 2; current++;
            if (!*current)
              usage();
            description_filename_override = *current;
        } else if (string_Eq(*current, "-subfile")) {
            argc -= 2; current++;
            if (!*current)
              usage();
            subscriptions_filename_override = *current;
        } else if (string_Eq(*current, "-nofork")) {
            argc--;
            dofork = 0;
        } else if (string_Eq(*current, "-reenter")) {
            argc--;                     /* just throw it away */
        } else if (string_Eq(*current, "-loc")) {
            argc -= 2; current++;
            if (!*current)
              usage();
            location_override = *current;
        } else
          *(new)++ = *current;
    }
    *new = *current;

#ifdef HAVE_ARES
    /*
     * Initialize resolver library
     */
    status = ares_init(&achannel);
    if (status != ARES_SUCCESS) {
        fprintf(stderr, "Couldn't initialize resolver: %s\n",
                ares_strerror(status));
        return(1);
    }
#endif

    /*
     * Initialize various subsystems in proper order:
     */
    dprintf("Initializing subsystems...\n"); /*<<<>>>*/
#ifdef CMU_ZWGCPLUS
    init_noticelist();
#endif
    mux_init();
    var_clear_all_variables(); /* <<<>>> */
    init_ports();       /* <<<>>> */
    dprintf("Initializing standard ports...\n");
    init_standard_ports(&argc, argv);
    if (argc>1)
      usage();
    dprintf("Initializing zephyr...\n");
    setup_signals(dofork);
    zephyr_init(notice_handler);

    if (dofork)
        detach();
    /*
     * Run the initprogs program(s) now that we are all set to deal:
     */
    dprintf("Running initprogs program...\n");
    run_initprogs();

    dprintf("Test Zwgc parser.\n\n");
    read_in_description_file();

    dprintf("Entering main loop\n");
    mux_loop();

    dprintf("Returning from main loop\n");
    finalize_zephyr();

    return(0);
}

/****************************************************************************/
/*                                                                          */
/*               :               */
/*                                                                          */
/****************************************************************************/

#define  USER_SUPPRESS     "SUPPRESS"
#define  USER_UNSUPPRESS   "UNSUPPRESS"

#ifdef CMU_ZCTL_PUNT
#define  USER_LIST_SUPPRESSED "LIST-SUPPRESSED"

#define PUNT_INC 1024
extern int_dictionary puntable_addresses_dict;
ZNotice_t punt_reply;

static void
create_punt_reply(int_dictionary_binding *punt_ent)
{
    int key_len = strlen(punt_ent->key);
    char *tmp;

    if (!punt_reply.z_message) {
        punt_reply.z_message = (char *)malloc(PUNT_INC);
        punt_reply.z_message[0] = 0;
    }

    if ((punt_reply.z_message_len + key_len + 1) / PUNT_INC >
        (punt_reply.z_message_len + PUNT_INC - 1) / PUNT_INC) {
        char *new_message = (char *)malloc((punt_reply.z_message_len
                                            / PUNT_INC + 1) * PUNT_INC);

        strcpy(new_message, punt_reply.z_message);

        free(punt_reply.z_message);
        punt_reply.z_message = new_message;
    }
    tmp = punt_reply.z_message + strlen(punt_reply.z_message);
    strcat (punt_reply.z_message, punt_ent->key);
    strcat (punt_reply.z_message, "\n");
    punt_reply.z_message_len += key_len + 1;

    while (*tmp != '\001') tmp++;
    *tmp = ',';
    while (*tmp != '\001') tmp++;
    *tmp = ',';
}
#endif /* CMU_ZCTL_PUNT */

void
notice_handler(ZNotice_t *notice)
{
#ifndef HAVE_ARES
    int ret;
    char node[NS_MAXDNAME];
#endif

#if defined(CMU_ZWGCPLUS)
    list_add_notice(notice);
#endif

#ifdef HAVE_ARES
    ares_getnameinfo(achannel,
                     (const struct sockaddr *)&(notice->z_sender_sockaddr),
                     notice->z_sender_sockaddr.sa.sa_family == AF_INET ?
                     sizeof(struct sockaddr_in) :
                     notice->z_sender_sockaddr.sa.sa_family == AF_INET6 ?
                     sizeof(struct sockaddr_in6) :
                     sizeof(notice->z_sender_sockaddr), ARES_NI_LOOKUPHOST,
                     notice_callback, notice);
#else
    ret = getnameinfo((const struct sockaddr *)&(notice->z_sender_sockaddr),
		      notice->z_sender_sockaddr.sa.sa_family == AF_INET ?
                      sizeof(struct sockaddr_in) :
                      notice->z_sender_sockaddr.sa.sa_family == AF_INET6 ?
                      sizeof(struct sockaddr_in6) :
                      sizeof(notice->z_sender_sockaddr),
		      node, sizeof(node), NULL, 0, 0);
    if (ret != 0)
	strcpy(node, "?");

    process_notice(notice, node);
#ifdef CMU_ZWGCPLUS
    /* Let list_del_notice clean up for us. */
#else
    ZFreeNotice(notice);
    free(notice);
#endif
#endif
}

#ifdef HAVE_ARES
/*
static void
notice_callback(void *arg,
                int status,
                int timeouts,
                struct hostent *fromhost)
*/
static void
notice_callback(void *arg,
                int status,
                int timeouts,
                char *node,
                char *service)
{
    ZNotice_t *notice = (ZNotice_t *) arg;

#ifdef CMU_ZWGCPLUS
    plus_set_hname(notice, node);
#endif

    process_notice(notice, node);
#ifdef CMU_ZWGCPLUS
    list_del_notice(notice);
#else
    ZFreeNotice(notice);
    free(notice);
#endif
}
#endif

static void
process_notice(ZNotice_t *notice,
               char *hostname)
{
    char *control_opcode;

    dprintf("Got a message\n");

    control_opcode = decode_notice(notice, hostname);
    if (control_opcode) {
#ifdef DEBUG
        printf("got control opcode <%s>.\n", control_opcode);
#endif
        if (!strcasecmp(control_opcode, USER_REREAD)) {
            read_in_description_file();
        } else if (!strcasecmp(control_opcode, USER_SHUTDOWN))
          zwgc_shutdown();
        else if (!strcasecmp(control_opcode, USER_STARTUP)) {
#ifdef DEBUG_MEMORY
            report_memory_usage(); /* <<<>>> */
#endif
            zwgc_startup();
        } else if (!strcasecmp(control_opcode, USER_SUPPRESS)) {
            string class = get_field(notice->z_message,
                                     notice->z_message_len, 1);
            string instance = get_field(notice->z_message,
                                        notice->z_message_len, 2);
            string recipient = get_field(notice->z_message,
                                         notice->z_message_len, 3);
            punt(class, instance, recipient);
            free(class);
            free(instance);
            free(recipient);
        } else if (!strcasecmp(control_opcode, USER_UNSUPPRESS)) {
            string class = get_field(notice->z_message,
                                     notice->z_message_len, 1);
            string instance = get_field(notice->z_message,
                                        notice->z_message_len, 2);
            string recipient = get_field(notice->z_message,
                                         notice->z_message_len, 3);
            unpunt(class, instance, recipient);
            free(class);
            free(instance);
            free(recipient);
#ifdef CMU_ZCTL_PUNT
        } else if (!strcasecmp(control_opcode, USER_LIST_SUPPRESSED)) {
            struct sockaddr_in old, to;
            int retval;

            if (!notice->z_port) {
                printf("zwgc: can't reply to LIST-SUPPRESSED request\n");
                return;
            }
            memset((char *) &punt_reply, 0, sizeof(ZNotice_t));
            punt_reply.z_kind = CLIENTACK;
            punt_reply.z_class = WG_CTL_CLASS;
            punt_reply.z_class_inst = "WG_REPLY";
            punt_reply.z_recipient = "zctl?";
            punt_reply.z_sender = "Zwgc";
            punt_reply.z_default_format = "";
            punt_reply.z_opcode = USER_LIST_SUPPRESSED;
            punt_reply.z_port = notice->z_port;
            punt_reply.z_message = NULL;
            punt_reply.z_message_len = 0;

            if (puntable_addresses_dict) {
                int_dictionary_Enumerate(puntable_addresses_dict,
                                         create_punt_reply);
            }

            old = ZGetDestAddr();
            to = old;

            to.sin_port = notice->z_port;
            if ((retval = ZSetDestAddr(&to)) != ZERR_NONE) {
                com_err("zwgc",retval,"while setting destination address");
                exit(1);
            }

            ZSendNotice(&punt_reply, ZNOAUTH);

            if ((retval = ZSetDestAddr(&old)) != ZERR_NONE) {
                com_err("zwgc",retval,"while resetting destination address");
                exit(1);
            }

            if (punt_reply.z_message) {
                free(punt_reply.z_message);
                punt_reply.z_message = NULL;
            }
#endif
        } else if (!strcasecmp(control_opcode, USER_EXIT)) {
            signal_exit(0);
        } else
          printf("zwgc: unknown control opcode %s.\n", control_opcode);

        goto cleanup;
    }

    if (!zwgc_active) {
#ifdef DEBUG
        if (zwgc_debug)
          printf("NON-ACTIVE: PUNTED <%s>!!!!\n", notice->z_class_inst);
#endif
        goto cleanup;
    }

    if (puntable_address_p(notice->z_class,
                           notice->z_class_inst,
                           notice->z_recipient)) {
#ifdef DEBUG
        if (zwgc_debug)
          printf("PUNTED <%s>!!!!\n", notice->z_class_inst);
#endif
        goto cleanup;
    }

    exec_process_packet(program, notice);
  cleanup:
    return;
}

#ifdef CMU_ZWGCPLUS
void
reprocess_notice(ZNotice_t *notice, char *hostname)
{
  list_add_notice(notice);
  process_notice(notice, hostname);
  list_del_notice(notice);
}
#endif

/***************************************************************************/

/*
 *
 */

static void
signal_exit(int ignored)
{
    mux_end_loop_p = 1;
}

/* clean up ALL the waiting children, in case we get hit with
   multiple SIGCHLD's at once, and don't process in time. */
static RETSIGTYPE
signal_child(int ignored)
{
#ifdef HAVE_WAITPID
  int status;
#else
  union wait status;
#endif
  int pid, old_errno = errno;

  do {
#ifdef HAVE_WAITPID
      pid = waitpid(-1, &status, WNOHANG);
#else
      pid = wait3(&status, WNOHANG, (struct rusage *)0);
#endif
  } while (pid != 0 && pid != -1);
  errno = old_errno;
}

/* rewrite the wgfile in case it has gone away */
static RETSIGTYPE
signal_usr1(int ignored)
{
    write_wgfile();
}

static void
setup_signals(int dofork)
{
#ifdef _POSIX_VERSION
    struct sigaction sa;

    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    if (dofork) {
        sa.sa_handler = SIG_IGN;
        sigaction(SIGINT, &sa, (struct sigaction *)0);
        sigaction(SIGTSTP, &sa, (struct sigaction *)0);
        sigaction(SIGQUIT, &sa, (struct sigaction *)0);
        sigaction(SIGTTOU, &sa, (struct sigaction *)0);
    } else {
        /* clean up on SIGINT; exiting on logout is the user's problem, now. */
        sa.sa_handler = signal_exit;
        sigaction(SIGINT, &sa, (struct sigaction *)0);
    }

    /* behavior never changes */
    sa.sa_handler = signal_exit;
    sigaction(SIGTERM, &sa, (struct sigaction *)0);
    sigaction(SIGHUP, &sa, (struct sigaction *)0);

    sa.sa_handler = SIG_IGN;
    sigaction(SIGPIPE, &sa, (struct sigaction *)0);

    sa.sa_handler = signal_child;
    sigaction(SIGCHLD, &sa, (struct sigaction *)0);

    sa.sa_handler = signal_usr1;
    sigaction(SIGUSR1, &sa, (struct sigaction *)0);

#else /* !POSIX */
    if (dofork) {
        /* Ignore keyboard signals if forking.  Bad things will happen. */
        signal(SIGINT, SIG_IGN);
        signal(SIGTSTP, SIG_IGN);
        signal(SIGTTOU, SIG_IGN);
        signal(SIGQUIT, SIG_IGN);
    } else {
        /* clean up on SIGINT; exiting on logout is the user's problem, now. */
        signal(SIGINT, signal_exit);
    }

    /* behavior never changes */
    signal(SIGTERM, signal_exit);
    signal(SIGHUP, signal_exit);
    signal(SIGCHLD, signal_child);
    signal(SIGPIPE, SIG_IGN);           /* so that Xlib gets an error */
    signal(SIGUSR1, signal_usr1);
#endif
}

/* detach() taken from old zwgc, with lots of stuff ripped out */

static void
detach(void)
{
  /* detach from terminal and fork. */
  register int i;

  /* Attempt to join the process group of the session leader.  This
   * will get us a HUP if the session leader is in the foreground at
   * logout time (which is often the case) or if the shell is running
   * under telnetd or xterm (both of which HUP the process group of
   * their child process).  If we have getsid(), that's the best way
   * of finding the session leader; otherwise use the process group of
   * the parent process, which is a good guess. */
#if defined(HAVE_GETSID)

  setpgid(0, getsid(0));
#elif defined(HAVE_GETPGID)
  setpgid(0, getpgid(getppid()));
#elif !defined(GETPGRP_VOID)
  setpgid(0, getpgrp(getppid()));
#endif

  /* fork off and let parent exit... */
  i = fork();
  if (i) {
      if (i < 0) {
          perror("zwgc: cannot fork, aborting:");
          exit(1);
      }
      exit(0);
  }
}
