/* server.c -- client/server backend
   $Id: server.c,v 1.17 2002/09/02 00:45:33 jsh Exp $

   Copyright (C) 1999 John Harper <john@dcs.warwick.ac.uk>

   This file is part of sawmill.

   sawmill 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, or (at your option)
   any later version.

   sawmill 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 sawmill; see the file COPYING.   If not, write to
   the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */

#include "sawmill.h"
#include "server.h"

#ifdef HAVE_UNIX

#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdarg.h>
#include <errno.h>

#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#endif

#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif

/* fd of the socket which clients connect to, or zero. */
static int socket_fd = -1;

/* pathname of the socket. */
static repv socket_name;

DEFSTRING(io_error, "server_make_connection:io");
DEFSYM(server_eval, "server-eval");

#define SOCK_IO(op, sock, buf, len)			\
	char *buf__ = (char *)buf;			\
	int todo__ = len;				\
	while(todo__ > 0) {				\
	    int this__ = op (sock, buf__, todo__);	\
	    if(this__ < 0) {				\
		if (errno != EINTR)			\
		    return -1;				\
	    }						\
	    else if(this__ == 0)			\
		break;					\
	    else {					\
		todo__ -= this__;			\
		buf__ += this__;			\
	    }						\
	}						\
	return len - todo__;

static int
sock_write (int fd, void *buf, size_t len)
{
    SOCK_IO (write, fd, buf, len);
}

static int
sock_read (int fd, void *buf, size_t len)
{
    SOCK_IO (read, fd, buf, len);
}

static void
server_handle_request(int fd)
{
    u_char req;
    if(sock_read(fd, &req, 1) != 1)
	goto disconnect;

    /* Need this in case the client code tries to execute a grab */
    save_timestamp (get_server_timestamp ());

    switch(req)
    {
	u_long len;
	repv val;

    case req_eval:
    case req_eval_async:
	/* 1. read length field
	   2. read LENGTH bytes of FORM
	   3. eval and print FORM
	   4. write length of result-string
	   5. write LENGTH bytes of result string */
	if(sock_read(fd, &len, sizeof(u_long)) != sizeof(u_long)
	   || (val = rep_make_string(len + 1)) == rep_NULL
	   || sock_read(fd, rep_STR(val), len) != len)
	    goto io_error;
	rep_STR(val)[len] = 0;
	val = rep_call_lisp1 (global_symbol_value (Qserver_eval), val);
	if (req != req_eval_async)
	{
	    if(val && rep_STRINGP(val))
	    {
		len = rep_STRING_LEN(val);
		if(sock_write(fd, &len, sizeof(u_long)) != sizeof(u_long)
		   || sock_write(fd, rep_STR(val), len) != len)
		    goto io_error;
	    }
	    else
	    {
		len = 0;
		if(sock_write(fd, &len, sizeof(u_long)) != sizeof(u_long))
		    goto io_error;
	    }
	}
	break;

    io_error:
	Fsignal(Qerror, rep_LIST_1(rep_VAL(&io_error)));
	return;

    case req_end_of_session:
    disconnect:
	rep_deregister_input_fd(fd);
	close(fd);
    }
    XFlush (dpy);
}

static void
server_accept_connection(int unused_fd)
{
    int confd;
    struct sockaddr_un addr;
    int addr_len = sizeof (addr);

    /* Linux manpage states that we can pass NULL for addr parameters,
       but that has been reported to crash on some systems.. */

    confd = accept(socket_fd, (struct sockaddr *) &addr, &addr_len);

    if(confd >= 0)
    {
	/* Once upon a time, I started reading commands here. I think
	   it's cleaner to just register CONFD as an input source */
	rep_register_input_fd(confd, server_handle_request);

	/* CONFD will inherit the properties of SOCKET-FD, i.e. non-
	   blocking. Make it block.. */
	rep_unix_set_fd_blocking(confd);
    }
}


/* initialisation */

void
server_init (void)
{
    char namebuf[256];
    repv user, name;

    rep_INTERN(server_eval);
    rep_mark_static (&socket_name);

    if (batch_mode_p ())
	return;

    name = Fsymbol_value (Qcanonical_display_name, Qt);
    user = Fuser_login_name ();

    if(!name || !user || !rep_STRINGP(name) || !rep_STRING(user))
	return;

#ifdef HAVE_SNPRINTF
    snprintf (namebuf, sizeof(namebuf), SAWMILL_SOCK_DIR, rep_STR(user));
#else
    sprintf (namebuf, SAWMILL_SOCK_DIR, rep_STR(user));
#endif

    /* Make the socket directory trying to ensure that it hasn't
       been compromised. */
    if (mkdir (namebuf, 0700) != 0)
    {
	if (errno == EEXIST)
	{
	    struct stat st;
	    if (stat (namebuf, &st) == 0)
	    {
		if (st.st_uid != getuid ())
		{
		    fprintf (stderr, "Owner of %s is not the current user\n",
			     namebuf);
		    return;
		}
		if (st.st_mode & (S_IRWXG | S_IRWXO))
		{
		    fprintf (stderr, "Permissions for %s are too lax\n",
			     namebuf);
		    return;
		}
	    }
	    else
	    {
		perror (namebuf);
		return;
	    }
	}
	else
	{
	    perror (namebuf);
	    return;
	}
    }

    /* Add the socket name */
    strcat (namebuf, "/");
    strcat (namebuf, rep_STR(name));

    /* Delete the socket if it exists */
    if(access(namebuf, F_OK) == 0)
    {
	/* Socket already exists. Delete it */
	unlink(namebuf);

	if (access (namebuf, F_OK) == 0)
	{
	    fprintf (stderr, "Can't delete %s\n", namebuf);
	    return;
	}
    }

    socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if(socket_fd >= 0)
    {
	struct sockaddr_un addr;
	addr.sun_family = AF_UNIX;
	strcpy(addr.sun_path, namebuf);
	if(bind(socket_fd, (struct sockaddr *)&addr,
		sizeof(addr.sun_family) + strlen(addr.sun_path) + 1) == 0)
	{
	    chmod (namebuf, 0700);
	    if(listen(socket_fd, 5) == 0)
	    {
		rep_unix_set_fd_nonblocking(socket_fd);
		rep_register_input_fd(socket_fd, server_accept_connection);

		socket_name = rep_string_dup (namebuf);
		return;
	    }
	    else
		perror ("listen");
	}
	else
	    perror ("bind");
	close(socket_fd);
    }
    else
	perror ("socket");
    socket_fd = -1;
}

void
server_kill (void)
{
    if(socket_fd > 0)
    {
	rep_deregister_input_fd(socket_fd);
	close(socket_fd);
	socket_fd = -1;
	unlink(rep_STR(socket_name));
	socket_name = rep_NULL;
    }
}

#endif /* HAVE_UNIX */
