/* 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>
 *
 *      Copyright (c) 1989 by the Massachusetts Institute of Technology.
 *      For copying and distribution information, see the file
 *      "mit-copyright.h".
 */

#include <sysdep.h>

RCSID("$Id: zephyr.c,v 1.18 2002/08/30 13:50:18 mattiasa Exp $");

#include <zephyr/mit-copyright.h>

/****************************************************************************/
/*                                                                          */
/*               Module containing code dealing with zephyr:                */
/*                                                                          */
/****************************************************************************/

#include <zephyr/zephyr.h>
#include <sys/socket.h>
#include <errno.h>
#include "new_string.h"
#include "zephyr.h"
#include "error.h"
#include "mux.h"
#include "subscriptions.h"
#include "variables.h"
#include "pointer.h"
#include "main.h"
#ifndef X_DISPLAY_MISSING
#include "X_driver.h"
#endif
#include "plus.h"

#ifdef DEBUG
extern int zwgc_debug;
#endif /* DEBUG */

/*
 *  Internal Routine:
 *
 *    string get_zwgc_port_number_filename()
 *        Effects: Returns the filename that the zwgc port # is/should be
 *                 stored in, based on the user's uid & the environment
 *                 variable WGFILE.  The returned string points into a
 *                 static buffer that may change on further calls to this
 *                 routine or getenv.  The returned string should not be
 *                 modified in any way.
 */

static string get_zwgc_port_number_filename()
{
    static char buffer[40];
    char *temp;

    temp = getenv("WGFILE");
    if (temp)
	return(temp);
    else {
	sprintf(buffer, "/tmp/wg.%d", getuid());
	return(buffer);
    }
}

/*
 *
 */

static void handle_zephyr_input(notice_handler)
     void (*notice_handler)();
{
    ZNotice_t *notice;
    struct sockaddr_in from;
    int complete_packets_ready;

    for (;;) {
	errno = 0;
	if ( (complete_packets_ready=ZPending()) < 0 ) {
	  warn("ZPending");
	  continue;
	}
    
	if (complete_packets_ready==0)
	  return;

	notice = malloc(sizeof(ZNotice_t));

	TRAP( ZReceiveNotice(notice, &from), "while getting zephyr notice" );
	if (!error_code) {
	    notice->z_auth = ZCheckAuthentication(notice, &from);
	    notice_handler(notice);
	}
        if (get_list_refcount(notice) == 0) {
            /* no windows created */
            if (!get_notice_fake(notice))
                ZFreeNotice(notice);
            free(notice);
        }
    }
}

static int zephyr_inited = 0;

/*
 * Write our pid to the portfile
 */

void
write_pid()
{
    char *filename;
    FILE *port_file;

    filename = get_zwgc_port_number_filename();
    port_file = fopen(filename, "a");
    if (port_file) {
        fprintf(port_file, "%d\n", getpid());
	fclose(port_file);
    } else {
	fprintf(stderr, "zwgc: error while opening %s for writing: ", filename);
	perror("");
    }
}
    
/*
 *
 */

void
zephyr_init(void (*notice_handler)(pointer))
{
    unsigned short port = 0;           /* Use any old port */
    const char *temp;
    const char *exposure;
    char *tty = NULL;
    FILE *port_file;
    int i;
    int nrealms;
    int ret;

    /*
     * Check if the windowgram file exist before doing initilization
     * to avoid expensive operations like dns resolving.
     */

    temp = get_zwgc_port_number_filename();
    errno = 0;
    port_file = fopen(temp, "r");
    if (port_file) {
        int tmpport;
	int pid;
	if (fscanf(port_file, "%d %d", &tmpport, &pid) != 2) {
	    fprintf(stderr, "zwgc: windowgram file already exists.  If you are\n");
	    fprintf(stderr, "zwgc: not already running zwgc, delete %s\n", temp);
	    fprintf(stderr, "zwgc: and try again.\n");
	    exit(1);
	} else {
	    ret = kill(pid, 0);
	    if (ret < 0 && errno == ESRCH) {
	        fprintf(stderr, "zwgc: windowgram file already exists, but matching process doesn't.\n");
		fprintf(stderr, "Removing file.\n");
		fclose(port_file);
		ret = unlink(temp);
		if(ret == -1) {
		    fprintf(stderr, "Failed to remove windowgram file.\n");
		    fprintf(stderr, "Remove it manually.\n");
		    exit(1);
		}
	    } else {
	      fprintf(stderr, "zwgc: another client already running, with pid %d\n", pid);
	      exit(1);
	    }
	}
    }

    /*
     * Initialize zephyr.  If error, print error message & exit.
     */
    FATAL_TRAP( ZInitialize(), "while initializing Zephyr" );
    FATAL_TRAP( ZOpenPort(&port), "while opening Zephyr port" );

    /*
     * Save away our port number in a special place so that zctl and
     * other clients can send us control messages: <<<>>>
     */

    port_file = fopen(temp, "w");
    if (port_file) {
	fprintf(port_file, "%d\n", port);
	fclose(port_file);
    } else {
	fprintf(stderr, "zwgc: error while opening %s for writing: ", temp);
	perror("");
    }

    /* Set hostname and tty for locations.  If we support X, use the
     * display string for the default tty name. */
    if (location_override)
	tty = location_override;
#ifndef X_DISPLAY_MISSING
    else if (dpy)
	tty = DisplayString(dpy);
#endif
    error_code = ZInitLocationInfo(NULL, tty);
    TRAP( error_code, "while initializing location information" );

    /*
     * Retrieve the user's desired exposure level (from the zephyr variable
     * "exposure"), convert it to the proper internal form then 
     * set the user's location using it.  If the exposure level is
     * not one of the allowed ones, print an error and treat it as
     * EXPOSE_NONE.
     */
    temp = ZGetVariable("exposure");
    if (temp) {
	if (!(exposure = ZParseExposureLevel(temp))) {
	    ERROR2("invalid exposure level %s, using exposure level none instead.\n", temp);
	    exposure = EXPOSE_NONE;
	}
    } else
      exposure = EXPOSE_NONE;

    error_code = ZGetRealmCount(&nrealms);
    if (error_code)
      TRAP( error_code, "ZGetRealmCount");

    for (i = 0; i < nrealms; ++i) {
	char *realm;

	error_code = ZGetRealmName(i, &realm);
	if (error_code)
	    TRAP( error_code, "ZGetRealmName");

	error_code = ZSetLocation(realm, exposure); /* <<<>>> */
	if (error_code != ZERR_LOGINFAIL)
	    TRAP( error_code, "while setting location" );
    }

    zwgc_startup();

    /*
     * Set $realm to our realm and $user to our zephyr username:
     */
    var_set_variable("realm", ZGetDefaultRealm());
    var_set_variable("user", ZGetSender());

    /*
     * <<<>>>
     */
    mux_add_input_source(ZGetFD(), (void (*)())handle_zephyr_input,
			 notice_handler);
    zephyr_inited = 1;
}

/*
 *
 */

void remove_port_number_file(void)
{
    char * temp;
    errno = 0;
    temp = get_zwgc_port_number_filename();
    unlink(temp);
    if (errno) {
	fprintf(stderr, "zwgc: error while trying to delete %s: ", temp);
	perror("");
    }
}

/*
 *
 */

void finalize_zephyr(void) /* <<<>>> */
{
    string temp;
    int i, cnt;
    char *realm;

    if (zephyr_inited) {

	/*
	 * Remove the file containing our port # since it is no longer needed:
	 */
	remove_port_number_file();

	/*
	 * Cancel our subscriptions, unset our location, and close our zephyr
	 * connection:
	 */

	TRAP(ZGetRealmCount(&cnt), "while getting realm count");
	if (error_code)
	    return;

	for (i=0; i<cnt; i++) {
	    TRAP(ZGetRealmName(i, &realm), "while getting realm name");
	    if (error_code)
		continue;
#ifdef DEBUG
	    if (zwgc_debug) {
		TRAP( ZUnsetLocation(realm), "while unsetting location" );
		TRAP( ZCancelSubscriptions(realm, 0),
		      "while canceling subscriptions" );
	    } else {
#endif /* DEBUG */
		ZUnsetLocation(realm);
		ZCancelSubscriptions(realm, 0);
#ifdef DEBUG
	    }
#endif /* DEBUG */
	}
	ZClosePort();
    }
}
