The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/* $Id: goofey.c,v 1.3 1997/03/18 02:22:21 tym Exp $ */
/*
 *
 * $RCSfile: goofey.c,v $
 *
@(#)goofey.c    2.08 Pluto Client code.
 * This code maintainns connections with the central pluto server
 * and is used to inform it of the users activity.
 *
 * Written: Darren Platt , Tim MacKenzie.
 *	Started: Sometime
 *
 * This was SCCS version: 2.02
 *
 * History:
 * Daz v0.00: Goofey gasps its first breath of CPU time.
 * tym v1.11: Added check of tty owner to exit conditions... problem on decs
 * tym v1.12: Now checks time since last keypress as condition of death.
 * tym v1.15: Now uses statics for the ttyname rather than getting it everytime
 * tym v1.17: Uses grandparent pid for death (got it right finally).
 * daz v1.17: Better command line parsing.
 * daz v1.18: Once more for good luck so that -s works correctly.
 * tym v1.19: Changed tty code, dont call ttyslot when we're not on a tty.
 * tym 31 Oct 1991: Modified addr code. Clock can tell goofey to stay around
 *      after a query. Now prompts for message of none given to -s.
 * daz v1.22: Changed so that exit codes are indicative of exit status.
 * tym  6 Apr 1992: Checks for tty, removed 'Arrived_message',
 *      No more static limits! Removed a lot of dead code.
 * tym  7 Apr 1992: Added '.' on line as exit condition for '-s'
 * tym  8 Apr 1992: Fixed the problem with pluto restarts (can accept() while
 *      waiting for pluto to come back to life).
 * tym 13 Apr 1992: Fixed problem with sgi's stuff getpw*
 * tym 14 Apr 1992: Another try at proper user identification.
 * tym 15 Apr 1992: Problem with idle time?
 * tym 15 Apr 1992: Now ignores SIGTTOU... stty tostop was killing it!
 * tym 10 Jun 1992: Restores tty modification times after writing if able.
 * tym 20 Jun 1992: Sends password if it is found.
 * tym 20 Jun 1992: Only allow connections from the server machine.
 * tym 12 Oct 1992: Added anthony's FIREWALL changes, protocol change.
 * tym  3 Dec 1992: Added GOOFEYUSER environment variable for rik.
 * tym  7 Dec 1992: Now bzero's sockaddr's before using them.
 * tym  8 Dec 1992: Ansi'ized ?
 * tym  7 Apr 1993: Checks result of read since linux is slightly broken.
 * tym 27 May 1993: Changed way that we declare funcs so it's neater :)
 * tym  7 Dec 1993: configure'ized. Added client options, width handling
 * tym 16 Dec 1993: added readline/getline support (M Battersby/A Cosgriff)
 * tym 23 Dec 1993: made non-blocking broken by default, fixed getenv proto
 * tym 26 Aug 1994: added GOOFEYLOC support
 * tym 29 Aug 1994: fixed problem with readline and eof.
 * tym 21 Jul 1995: fixed problem with long lines and readline/getline
 * tym 22 Sep 1995: Added editor patches from Karl, extended them somewhat
 * tym 23 Sep 1995: Allow ~r to read output from command
 * tym  2 Oct 1995: Fixed to keep non-ansi compilers happy.
 * tym  6 Oct 1995: Fixed ~c... stupid ?: precedence...
 * tym  3 Apr 1996: Added autosplit
 * tym 26 Sep 1996: Added "I don't know" idletime.
 * tym 29 Oct 1996: Added variable timeout and re-register code
 * tym 24 Feb 1997: Fixed small problem with GOOFEY_ARGS
 * tym 18 Mar 1997: Changed hostname and added session_pwd stuff
 */

#define VERSION "3.15"
#define CLIENT_TYPE 'G'
#define PWD_FILE ".goofeypw"

static char *id_string = "$Id: goofey.c,v 1.3 1997/03/18 02:22:21 tym Exp $";

#ifndef MAX_WAIT
#define MAX_WAIT 20
#endif
int max_wait = MAX_WAIT;

#define NEWSERVER "130.194.9.2"
#ifndef SERVER_HOSTNAME
#define SERVER_HOSTNAME "pluto.cc.monash.edu.au"
#endif

#ifndef SERVER_PORT_NO
#define SERVER_PORT_NO 3987
#endif

#define LONELY_INTERVAL 60
#define MAX_RETRIES 10
#define SERVER_MAXREQ 999 /* Maximum size message server will handle */
#define I_DONT_KNOW 200000000

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <netdb.h>
#include <stdio.h>
#include <assert.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <ctype.h>
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif
#include <errno.h>
extern int errno;

#ifdef READLINE
#include <readline/readline.h>
#else
#ifdef GETLINE
#include <getline.h>
#endif
#endif

#define TRUE 1
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#include <stddef.h>
#endif

/* Wierd macros for handling ansi/non-ansi prototypes */
#if ANSI || __GNUC__ || __STDC__
#define AND ,
#define DEFUN(a,b) ( b )
#define VOIDFUNC ( void )
#define _PROTO(a) a
#else
#define AND ;
#define DEFUN(a,b) a b;
#define _PROTO(a) ()
#define VOIDFUNC ( )
#endif

/* For firewall use: 
 * Define FIREWALL to be the minumum local port to use for outgoing connections
 * and FIREWALL_MAX to be the maximum port. LOCAL_PORT - LOCAL_PORT_MAX are
 * the port range over which we will accept connections for resident goofeys
 */
#ifdef FIREWALL
# ifndef FIREWALL_MAX
#  define FIREWALL_MAX FIREWALL
# endif
# ifndef LOCAL_PORT
#  define LOCAL_PORT FIREWALL
# endif
# ifndef LOCAL_PORT_MAX
#  define LOCAL_PORT_MAX FIREWALL_MAX
# endif
#endif

#ifndef LOCAL_PORT
# define LOCAL_PORT 0
#endif

#ifndef LOCAL_PORT_MAX
# define LOCAL_PORT_MAX LOCAL_PORT
#endif

/* Portability stuff: if we don't have fd_set or FD_SET,etc macros
 * then make them up.
 */
#ifndef FD_SET
# ifndef SUNOS3
typedef struct fd_set { int fds_bits[1]; }  fd_set;
# endif
# define FD_ZERO(fd) ((fd)->fds_bits[0] = 0)
# define FD_SET(b, fd) ((fd)->fds_bits[0] |= 1 << (b))
# define FD_ISSET(b, fd) ((fd)->fds_bits[0] & 1 << (b))
# define FD_SETSIZE 32
#endif

/* Handle bsd/sysv differences with string handling */
#if STDC_HEADERS || HAVE_STRING_H
#include <string.h>
/* An ANSI string.h and pre-ANSI memory.h might conflict.  */
#if !STDC_HEADERS && HAVE_MEMORY_H
#include <memory.h>
#endif /* not STDC_HEADERS and HAVE_MEMORY_H */
#define index strchr
#define rindex strrchr
#define bcopy(s, d, n) memcpy ((d), (s), (n))
#define bcmp(s1, s2, n) memcmp ((s1), (s2), (n))
#define bzero(s, n) memset ((s), 0, (n))
#else /* not STDC_HEADERS and not HAVE_STRING_H */
#include <strings.h>
/* memory.h and strings.h conflict on some systems.  */
#endif /* not STDC_HEADERS and not HAVE_STRING_H */

#ifndef RETSIGTYPE
#define RETSIGTYPE void
#endif

static struct sockaddr_in pluto_host;
char * build_arrived_string _PROTO((int));
void check_alloc _PROTO((void *, int));
int connect_to_server _PROTO((void));
void do_resize _PROTO((char **,int *,int,char *));
void establish_signal_handlers _PROTO((void));
char * get_my_tty _PROTO(());
void go_find_a_port _PROTO((void));
void print_help _PROTO((void));
void process_flags _PROTO((char **));
void request_handler _PROTO((void));
void say_hi_to_server _PROTO((int));
long when_last_keypress _PROTO((void));
void write_str _PROTO((int, char *));
void check_size _PROTO((void));
void process_env _PROTO((void));
void generate_password _PROTO((void));
char * get_message _PROTO((char **));
int write_goof _PROTO((char *s , char *name));
extern char *getenv();

int    local_sock;
int    ppid; /* Parent process id */
int    port_no; /* Port number we have reserved for ourselves at this end */
int    want_help;
int    reconnect_retries; /* Number of tries left to reconnect to the server */
int    width; /* Width I think my terminal is */
int    multipart; /* A kluge to handle multipart messages */
int    lonely_timer; /* How long is it since we've spoken/heard from server? */

#define REREGISTER_TIME 600 /* How long to wait before re-registering */

#define RESIZE(m,l,x,s) do_resize(&m,&l,x,s)

/* Where the server resides. Can be changed at command line. */
char machine_name[50] = SERVER_HOSTNAME;
int port_number = SERVER_PORT_NO;
char optstring[100] = "P";
int broke_shutdown;
char session_pwd[20];

int
main DEFUN((argc,argv), int argc AND char ** argv)
{
    check_size();
    process_env();
    process_flags(argv);  /* This may exit() if goofey is non-resident */
    if (when_last_keypress() < 0)  {
	fprintf(stderr,
	    "\rgoofey: Parent process died, exiting\n");
	exit(0);
    }
    go_find_a_port();			/* Assumed to work or die. */
    generate_password();
    say_hi_to_server(1);

    ppid = getppid();

    if (fork()) {
	exit(0); /* In parent */
    }
    else {
	fd_set mask;
	struct timeval tv;
	establish_signal_handlers(); 
	while(1) {
	    request_handler();
	    while (reconnect_retries) {
		int i;
		FD_ZERO(&mask);
		FD_SET(local_sock, &mask);
		tv.tv_sec = LONELY_INTERVAL;
		tv.tv_usec = 0;
		i=select(local_sock+1,&mask,0,0,&tv);
		if (i==1)
		    request_handler();
		if (i<0 && errno != EINTR) {
		    fprintf(stderr,"\rSomething has happened to my socket!\n");
		    exit(1);
		}
		say_hi_to_server(0);
	    }
	}
    }
}

void
check_size VOIDFUNC
{
#ifdef TIOCGWINSZ
    struct winsize ws;
#endif
    
    if (isatty(1)) {
	char *s;
#ifdef TIOCGWINSZ
	if (ioctl(1,TIOCGWINSZ,&ws))
	    return;
	width = ws.ws_col;
#endif
	s = getenv("COLUMNS");
	if (s)
	    width = atoi(s);
    }
}

int
autoSplit VOIDFUNC
{
    return strchr(optstring, 'A') != 0;
}

void
say_hi_to_server DEFUN((disp), int disp)
{
    int	sock;
    char	*mesg;
    char buf[100];
    int  i;

    sock = connect_to_server();	 /* Works or dies. */
    if (sock < 0) return;
    mesg = build_arrived_string(1);
    if (broke_shutdown) {
	sprintf(buf,"!%03x",strlen(mesg));
	write_str(sock,buf);
	write_str(sock,mesg);    /* Send the initial message */
	write_str(sock,"!000");  /* Send "eof" */
    } else {
	write_str(sock,mesg);    /* Send the initial message */
	shutdown(sock,1); /* Send Eof */
    }
    while ((i = read(sock,buf,(sizeof buf)-1)) > 0) {
	buf[i] = 0;
	if (disp)
	    printf("%s",buf);
	if (broke_shutdown && !buf[i-1])
	    break;
    }
    fflush(stdout);
    close(sock);
}

void
generate_password VOIDFUNC
{
    char *tmp = build_arrived_string(0);
    int i;
    unsigned long seed = time(0L);
    seed ^= (getpid() << 16) | (getppid());
    for (i=0;tmp[i];i++)
	seed ^= (tmp[i] << ((i%4) * 8));
    sprintf(session_pwd, "%08lx", seed);
}

void
die VOIDFUNC
{
    int sock;
    char buff[1024];
    sock = connect_to_server();	 /* Works or dies. */
    sprintf(buff,"%s,%s",build_arrived_string(0),"-2");
    write_str(sock,buff);
    close(sock);/* Say bye to server */
    exit(0);
}

RETSIGTYPE
sigdie DEFUN((sig), int sig)
{
    fprintf(stderr,"\rgoofey: received signal %d, exiting\n",sig);
    die();
}

void
send_and_get_message DEFUN((str1,str2), char * str1 AND char * str2)
{
    int sock;
    fd_set tm;
    struct timeval tout;
    int dont_exit = 0;
    char buff[1025];
    char *b;
    int i;

    sock = connect_to_server();	 /* Works or dies. */
    if (sock < 0) return;
    b = malloc(i=(strlen(build_arrived_string(0)) +
		  strlen(str1) +strlen(str2) + 4));
    check_alloc(b,i);
    if (i > SERVER_MAXREQ) {
	fprintf(stderr,"goofey: server may truncate message. \
Saving to 'dead.goofey' just in case\n");
	if (write_goof(str2,"dead.goofey")) {
	    fprintf(stderr,"goofey: unable to write to file 'dead.goofey'\n");
	}
    }
    sprintf(b,"%s,%s %s\n",build_arrived_string(0),str1,str2);
    if (broke_shutdown) {
	sprintf(buff,"!%03x",strlen(b));
	write_str(sock,buff);
	write_str(sock,b);
	write_str(sock,"!000");
    } else {
	write_str(sock,b);
	shutdown(sock,1); /* Send eof */
    }
    free(b);
    i=1;
    FD_ZERO(&tm);
    FD_SET(sock,&tm);
    while (i>0) {
	tout.tv_usec= 0;
	tout.tv_sec = max_wait;

	if (select(sock +1,&tm,0,0,&tout)<=0) {
	    printf("Pluto failed to reply within %d seconds\n",max_wait);
	    break;
	}
	i = read(sock,buff,(sizeof buff)-1);
	if (i<0) break;
	buff[i]=0;
	if (i && buff[0] == 1) dont_exit = 1;
	printf("%s",buff);
	fflush(stdout);
	if (broke_shutdown && !buff[i-1])
	    break;
    }
    close(sock);/* Say bye to server */
    if (dont_exit) return;
    if (multipart) return;
    exit(0);  /* The normal exit point for non-resident goofeys */
}
    
RETSIGTYPE
lonely DEFUN((ignored), int ignored)
{ /* Am I lonely and forgotten ? */
    int i;
    i = when_last_keypress();

    if (i < 0) {
	fprintf(stderr,"\rgoofey: parent process died, exiting\n");
	die();
    }

    signal(SIGALRM,lonely);
    alarm(LONELY_INTERVAL);
    lonely_timer += LONELY_INTERVAL;
    if (lonely_timer > REREGISTER_TIME) {
	int tmp = reconnect_retries;
	reconnect_retries = 2; /* just in case this fails */
	say_hi_to_server(0);
	reconnect_retries = tmp;
    }
}

int
allspace DEFUN((s), char *s)
{
    while (s && *s && isspace(*s)) s++;
    if (!s || !(*s)) return 1;
    return 0;
}

char *
get_users_name VOIDFUNC
{/* Find out the users name, do not modify this code unless absolutely
  * necessary (for your own protection)
  */
    static char *name;
    if (name) return name;
    if (!name || allspace(name))
	name = getenv("GOOFEYUSER");
    if (!name || allspace(name))
	name = getenv("USER");
    if (!name || allspace(name))
	name = getenv("LOGNAME");
    if (!name || allspace(name))
	name = "unknown";
    return name;
}

char *
build_arrived_string DEFUN((force), int force)
{
    /* Construct an arrived message giving details of the assigned port
     * number and the username etc. */

    static char data[300];
    char buf[100];
    char *loc,*s;
    struct stat st;
    static done,port,wid;

    if (done && port==port_no && wid == width && !force) return data;
    done =1;
    port = port_no;
    wid = width;
    sprintf(buf,"%s/%s",getenv("HOME"),PWD_FILE);
    if (stat(buf,&st))
	buf[0]=0;
    else {
	FILE *f;
	if (st.st_mode & 077) {
	    fprintf(stderr,"goofey: warning %s should have permission 0600!\n",
		buf);
	    fprintf(stderr,"goofey: type 'chmod 600 %s' to fix this\n",buf);
	}
	f = fopen(buf,"r");
	buf[0] = 0;
	if (f) {
	    fread(buf,1,10,f);
	    fclose(f);
	    buf[10] = 0;
	}
    }
	
    if (strchr(optstring,'S'))
	broke_shutdown = 1;

    loc = getenv("GOOFEYLOC");
    if (loc)
	for (s = strchr(loc, '&') ; s ; s = strchr(s+1, '&'))
	    *s = '*';
    
    sprintf(data,"#%c%s%s%s,|%d,%s%s%s#%s,%s,%s,%d,%s",CLIENT_TYPE,VERSION,
	*optstring?",@":"",optstring,
	width, 
	loc?"&":"", loc?loc:"", loc?"&,":"",
	session_pwd,
	get_users_name(),buf,port_no,get_my_tty() );
    return data;
}

void
go_find_a_port VOIDFUNC
{
    /* Allocate a port that the server can query this client through */

    int length;
    int count;
    struct sockaddr_in local_server;

    local_sock = socket(AF_INET,SOCK_STREAM,0);
    if (local_sock <0) {
	perror ("Opening stream socket :- couldn't get a socket");
	exit(1);
    }

    bzero(&local_server,sizeof local_server);
    local_server.sin_family = AF_INET;
    local_server.sin_addr.s_addr = INADDR_ANY;

    for (count = LOCAL_PORT; count <= LOCAL_PORT_MAX;count++) {
	local_server.sin_port = htons(count);
	if (bind(local_sock,(struct sockaddr *) &local_server,
		 sizeof local_server) == 0)
	    break;
    }
    if (count > LOCAL_PORT_MAX) {
	fprintf(stderr,
	    "goofey: couldn't allocate local port\n");
	exit(1);
    }

    length = sizeof local_server;
    if (getsockname(local_sock,(struct sockaddr *)&local_server,&length) <0) {
	perror("getsockname");
	exit(1);
    } /* Have established a port which the clock local_server may use to
	* interrogate this process.
       */

    port_no = ntohs(local_server.sin_port);
#ifdef DIAGNOSTICS
    fprintf(stderr,"%s\n",inet_ntoa(local_server.sin_addr));
    printf("Socket port #%d\n",ntohs(local_server.sin_port));
#endif
}

long 
when_last_keypress VOIDFUNC
{
    struct stat my_stat;
    if (kill(ppid,0)) return -3;  /* My daddy is dead :( */
    if (stat(get_my_tty(),&my_stat)) return I_DONT_KNOW;
    if (time(0) < my_stat.st_mtime) return 0;
    return time(0) - my_stat.st_mtime;
}

char *
get_my_tty VOIDFUNC
{
    char *ttyname();
    char *s;
    static set;
    static char buffer[30];

    if (set) return buffer;
    s = ttyname(0);
    if (!s) strcpy(buffer,"/dev/notatty");
    else strcpy(buffer,s);
    set =1;
    return buffer;
}

void
request_handler VOIDFUNC
{
    int msgsock;
    char buf[1024];
    struct sockaddr_in addr;
    int len = sizeof addr;
    char *msg=0;
    int rval,size=0;
    long int last=0;

    msgsock = accept(local_sock,(struct sockaddr *)&addr,(int *)&len);
    if (msgsock == -1) 
	return;

    lonely_timer = 0;

    do {
	if ((rval = read(msgsock,buf,1023)) < 0) {
	    if (errno != EINTR) {
		perror("Handling clock request.");
		break;
	    }
	} else {
	    if (size == 0) {
		if (strchr("ZW",*buf)) {
		    char tmp[100];
		    sprintf(tmp,"%ld\n",last=when_last_keypress());
		    write_str(msgsock,tmp);
		}
	    }
	    RESIZE(msg,size,rval,buf);
	}
    } while (rval != 0);
    if (last < 0) {
	fprintf(stderr,"\rGoofey: parent process has died, exiting\n");
	exit(0);
    }
    if (msg) {
	if (*msg && strncmp(msg+1, session_pwd, strlen(session_pwd))) {
	    fprintf(stderr,"\n\rGoofey: Message received from %s with invalid session password (intruder?)\n",inet_ntoa(addr.sin_addr));
	    free(msg);
	    close(msgsock);
	    return;
	}
	msg[size] = 0;
	switch(msg[0]) {
	    case 'S':
		/* Attempt to reconnect to clock at 1 minute intervals */
		reconnect_retries=MAX_RETRIES;
		break;
	    case 'W':
		/* Return time since last keypress */
		break;

	    case 'Z':
		/* Why not. */
	    {
		struct stat times;
		time_t timeout[2];
		int doit;
		doit = stat(get_my_tty(),&times); 
		    /* Store the access and mod times */
		puts(msg+1+strlen(session_pwd)); /* Output a message */
		/* Reset the access and modify time to value before blatting */
		if (!doit) {
		    timeout[0] = times.st_atime;
		    timeout[1] = times.st_mtime;
		    utime(get_my_tty(),timeout);
		}
		break;
	    }

	    case 'E':
		/* Exit */
		close(local_sock);
		close(msgsock);
		sleep(1);
		fprintf(stderr,
	    "\rgoofey: resident goofey killed by server at your request\n");
		exit(0);

	    default:
		write_str(msgsock,"Unknown command to goofey\n");
	}
	free(msg);
    }
    close(msgsock);
}

void
find_inet_addr DEFUN((ad), struct sockaddr_in * ad)
{/* Find address by lookup __once__ */
    struct hostent *hp, *gethostbyname();
    static int done=0;   /* Have I found it yet? */
    ad->sin_family = AF_INET;
    if (done) {
	ad->sin_addr.s_addr = pluto_host.sin_addr.s_addr;
	return;
    }

    pluto_host.sin_addr.s_addr = inet_addr(machine_name); 
	/* Is it a 130.x.x.x ? */
    if (pluto_host.sin_addr.s_addr != -1) { /* YES! */
	pluto_host.sin_family = AF_INET;
    } else {/* Must be a hostname */
	hp = gethostbyname(machine_name);
	if (hp) {
	    pluto_host.sin_family = hp->h_addrtype;
#if     defined(h_addr)	 /* In 4.3, this is a #define */
	    bcopy(hp->h_addr_list[0],(caddr_t)&pluto_host.sin_addr,
		 hp->h_length);
#else   /* defined(h_addr) */
	    bcopy(hp->h_addr,(caddr_t)&pluto_host.sin_addr, hp->h_length);
#endif  /* defined(h_addr) */
	} else {
	    fprintf(stderr,"%s: unknown host\n", machine_name);
	    exit(2);
	}
    }
    ad->sin_addr.s_addr = pluto_host.sin_addr.s_addr;
    done =1;
}

int
connect_to_server VOIDFUNC
{
    /* Establish connection with the central server */

    int sock;
    int res;
    struct sockaddr_in server;

    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock<0) {
	perror("opening stream socket");
	exit(1);
    }
    bzero(&server,sizeof server);
    server.sin_family = AF_INET;
#ifdef FIREWALL
    {
	int count;
	server.sin_addr.s_addr = INADDR_ANY;
	for (count = FIREWALL; count <= FIREWALL_MAX;count++) {
	    server.sin_port = htons(count);
	    if (bind(sock,(struct sockaddr *) &server,
		     sizeof server) == 0)
		break;
	}
	if (count > FIREWALL_MAX) {
	    fprintf(stderr,
		"goofey: couldn't allocate local port for firewall\n");
	    exit(1);
	}
    }
#endif

    find_inet_addr(&server);
    server.sin_port = htons(port_number);

#define NONBLOCK_BROKE
#ifndef NONBLOCK_BROKE
# ifdef FNDELAY
    if (fcntl(sock,F_SETFL,FNDELAY))
# else
    if (fcntl(sock,F_SETFL,O_NONBLOCK))
# endif
    {
	perror("Setting non blocking mode on connecting socket");
	exit(1);
    }
#endif

    res = connect(sock,(struct sockaddr *)&server,sizeof server);
#ifndef NONBLOCK_BROKE
    /* These #if's are a bit ugly ... if nonblocking is broken for some
     * reason then don't fcntl and don't select.
     */
    signal(SIGPIPE,SIG_IGN);
    if ( res < 0 && (errno == EWOULDBLOCK || errno == EAGAIN)) {
	fd_set tm;
	struct timeval tout;
	tout.tv_usec= 0;
	tout.tv_sec = max_wait;
	FD_ZERO(&tm);
	FD_SET(sock,&tm);
	res =  select(sock+1,0,&tm,0,&tout);
	if (res > 0) {
	    res = connect(sock,(struct sockaddr *)&server,sizeof server);
	    if (res < 0 && errno == EISCONN)
		res = 1;
	}
    } else 
#else
    /* If non blocking is broken then set res to 1 if the connect succeeded */
    if ( ! res)
#endif
    {
	res = 1;
    }
	
    if (res <= 0) {
	if (reconnect_retries > 1) {
	    close(sock);
	    sock = -1;
	    reconnect_retries--;
	} else if (reconnect_retries == 1) {
	    fprintf(stderr,"\rGoofey: lost connection to server\n");
	    exit(1);
	} else {
	    if (res < 0)
		fprintf(stderr,
		    "goofey: The server is currently unavailable.\n");
#ifndef NONBLOCK_BROKE
	    else
		fprintf(stderr,
		    "goofey: Timed out while connecting to server.\n");
#endif
	    if (want_help) print_help();
	    exit(1);
	}
    } else {
	reconnect_retries=0;
	lonely_timer = 0;
    }
#ifndef NONBLOCK_BROKE
    signal(SIGPIPE,SIG_DFL);
    fcntl(sock,F_SETFL,0);
#endif
    return sock;
}

void
write_str DEFUN((sock,mesg),int sock AND char *mesg)
{
    if (write(sock,mesg,strlen(mesg)) <0)
	perror("writing on stream socket");
}

#ifdef SIGWINCH
RETSIGTYPE
size_change DEFUN((ignored),int ignored)
{
    int owidth;
    owidth = width;
    check_size();
    say_hi_to_server(0);
}
#endif

void
establish_signal_handlers VOIDFUNC
{
    signal(SIGINT,sigdie);
    signal(SIGQUIT,sigdie);
    signal(SIGHUP,sigdie);
    signal(SIGALRM,lonely);
    signal(SIGTTOU,SIG_IGN);
#ifdef SIGWINCH	
    signal(SIGWINCH,size_change);
#endif
    alarm(LONELY_INTERVAL);

    listen(local_sock,5);
}

/* -------------------------------------------------------------------- *
 * Command line processing					      *
 * -------------------------------------------------------------------- */

int
check_split_arg DEFUN((argv,i),char ** argv AND int * i)
{
    /* Some people get stressed when asked to provide options immediately
     * after the command line flag, so we will give them the option of
     * supplying the option with a space !!
     * This routine advances the i variable if necessary etc.
     */

     ++argv[*i]; /* Advance past the flag itself */
     if (!strlen(argv[*i])) {
	/* No more string left for this arg, so try the next one. */
	(*i)++;
	if (!argv[*i]) {
	    /* No more args left, how tragic. */
	    return 0;
	}
	return 1;
    }
    /* Argument follows the flag */
    return 1;
}

struct { char *name; char *opt; } longopts[] = {
    {"no-blat", "B"},
    {"only-one", "1"},
    {"return-response", "R"},
    {"inform-replace", "I"},
    {"forget-fail", "F"},
    {"tag-success", "T"},
    {"format-outgoing","f"},
    {"shutdown-broke","S"},
    {"ignore-alias","E"},
    {"auto-split","A"},
    {0,0}
};

void
process_env VOIDFUNC
{
    char *tmpargv[40];
    char *env = getenv("GOOFEY_ARGS");
    int i = 1;
    if (!env)
	return;
    tmpargv[0] = "goofey";
    while (*env) {
	while (*env && isspace(*env)) env++;
	if (!*env) break;
	if (*env != '+' && !(*env == '-' && env[1] == '-')) {
	    fprintf(stderr,
	"goofey: GOOFEY_ARGS may only contain '+' or '--' args. Ignored\n");
	    return;
	}
	tmpargv[i++] = env;
	while (*env && !isspace(*env)) env++;
	if (*env) {
	    *env = 0;
	    env++;
	}
	if (i == 40 - 1) {
	    fprintf(stderr,"goofey: GOOFEY_ARGS too long, truncated\n");
	    break;
	}
    }
    if (i <= 1)
	return;
    tmpargv[i] = 0;
    process_flags(tmpargv);
}
    
void 
process_flags DEFUN((argv),char **argv)
{
      /* Process flags sequentially */
      int i;
      int ignore_rest = 0;
      int send_message = 0;
      char *m=0;
      int len=0;
      int got_args=0;

      for(i=1;argv[i]!=NULL;i++) {
	    if (i && argv[i-1] == 0) {
		fprintf(stderr,"goofey: missing argument to option\n");
		exit(1);
	    }
	    if (ignore_rest) {
		 /* we have found a piece of plain text */
		RESIZE(m,len,strlen(argv[i]),argv[i]);
		RESIZE(m,len,1," ");
		got_args = 1;
		continue;
	    } else if (*argv[i] == '+') {
		strcat(optstring,argv[i]+1);
		continue;
	    } else if (*argv[i] != '-') {
		fprintf(stderr,"Expected an argument beginning with '-'\n");
		exit(1);
		break;
	    } else if (argv[i][1] == '-') {
		/* Handle --options */
		char *s; 
		int j;
		argv[i]+=2;
		s = argv[i];
		if (!strncmp(s,"port",4) || (*s == 'p' && !isalpha(s[1]))) {
		    if ((s = strchr(s,'='))) {
			port_number = atoi(s+1);
		    } else {
			port_number = atoi(argv[++i]);
		    }
		    continue;
		} else if (!strncmp(s,"machine",7) ||
			(*s == 'm' && !isalpha(s[1]))) {
		    if ((s = strchr(s,'='))) {
			strcpy(machine_name,s+1);
		    } else {
			if (argv[++i])
			    strcpy(machine_name,argv[i]);
		    }
		    continue;
		} else if (!strncmp(s,"width",5)) {
		    if ((s = strchr(s,'='))) {
			width = atoi(s+1);
		    } else {
			width = atoi(argv[++i]);
		    }
		    continue;
		} else if (!strncmp(s,"timeout",7)) {
		    if ((s = strchr(s,'='))) {
			max_wait = atoi(s+1);
		    } else {
			max_wait = atoi(argv[++i]);
		    }
		    continue;
		} else {
		    for (j = 0;longopts[j].name;j++)
			if (!strcmp(s,longopts[j].name)) {
			    strcat(optstring,longopts[j].opt);
			    break;
			}
		    if (longopts[j].name)
			continue;
		}
	    }
	    while (*(++argv[i])!= 0) {
	     switch(*(argv[i])) {
		case 'v':
		    printf("Goofey version %s\n",VERSION);
		    break;
		case 's':
		    /* Send message to user */
		    if (!check_split_arg(argv,&i)) {
			fputs("Expected user's name.\n",stderr);
			print_help();
			exit(1);
		    }
		    RESIZE(m,len,2,"*s");
		    RESIZE(m,len,strlen(argv[i]),argv[i]);
		    RESIZE(m,len,1," ");
		    send_message = ignore_rest = 1;
		    *(argv[i]+1)= 0;
		    break;
		case 'h':
		    want_help = 1;
		default: {
                    XXX
		    /* Goofey doesn't understand the command, so send it to the
		     * clock. */
		    
		    RESIZE(m,len,1,"*");
		    RESIZE(m,len,strlen(argv[i]),argv[i]);
		    RESIZE(m,len,1," ");
		    *(argv[i]+1)= 0;
		    ignore_rest = 1;
		}
	    }
	}
    }
    if (m) {
	char *n;
	if (send_message && !got_args) {
	    n = get_message(&m);
	    if (!n) {
		fprintf(stderr,"goofey: Empty message: not sent!\n");
		exit(0);
	    }
	    if (autoSplit()) {
		int otherlen = strlen(m) + strlen(build_arrived_string(0)) + 5;
		if (strlen(n) + otherlen > SERVER_MAXREQ) {
		    char *tmp, *end, *orig, old;
		    int part = 0;
		    fprintf(stderr, 
			"goofey: message exceeds maximum, autosplitting...\n");
		    tmp = n;
		    multipart = 1;
		    while (*tmp) {
			end = tmp + strlen(tmp);
			if (end - tmp > SERVER_MAXREQ - otherlen) {
			    end = tmp + SERVER_MAXREQ - otherlen;
			}
			orig = end;
			while (end > tmp && *end && *end != '\n') end--;
			if (end == tmp) {
			    end = orig;
			} else if (*end) {
			    end++;
			}
			old = *end;
			*end = 0;
			part++;
			fprintf(stderr,"goofey: sending part %d\n", part);
			send_and_get_message(m,tmp);
			*end = old;
			tmp = end;
		    }
		    fprintf(stderr,"goofey: Message sent in %d parts\n",part);
		    exit(0);
		} else {
		    send_and_get_message(m,n?n:"");
		}
	    } else {
		send_and_get_message(m,n?n:"");
	    }
	    if (n)
		free(n);
	} else {
	    send_and_get_message(m,"");
	}
	free(m);
    }
}

char *
editor VOIDFUNC
{
    int l;
    static char *e;
    char *ed;
    if (e)
	return e;
    ed=getenv("VISUAL");
    if (!ed)
    {
	ed=getenv("EDITOR");
	if (!ed)
	    ed="vi";
    }
    l=strlen(ed);
    e=malloc(++l);
    check_alloc(e,l);
    strcpy(e, ed);
    return e;
}

int
write_goof DEFUN((s, name), char *s AND char *name)
{
    int fd;
    int len=0;
    fd = creat(name, 0600);
    if (fd < 0) {
	return 1;
    }
    if (s) {
	len = strlen(s);
	if (len != write(fd, s, len)) {
	    unlink(name);
	    close(fd);
	    return 1;
	}
    }
    close(fd);
    return 0;
}

char *
editmsg DEFUN((msg,len), char * msg AND int * len)
{
    FILE *fp = 0;
    char tmpfile[32];
    char buf[500];
    int i;

    for (i = 'a';i<='z';i++) {
	sprintf(tmpfile, "/tmp/goof%05lu%c", (long)getpid(), i);
	if (!write_goof(msg, tmpfile)) {
	    i = 0;
	    break;
	}
    }
    if (i) {
	fprintf(stderr, "goofey: unable to create file '%s'\n", tmpfile);
	return msg;
    }
    sprintf(buf, "%s %s", editor(), tmpfile);
    fprintf(stderr,"goofey: invoking editor (%s)...\n",buf);
    system(buf);
    fp=fopen(tmpfile,"r");
    if (!fp) {
	fprintf(stderr, "\rgoofey: temp file cannot be accessed\n");
	return msg;
    }

    free(msg);	/* We can discard existing msg now */
    msg=0;
    *len=0;
    while (fgets(buf, sizeof(buf)-1, fp)!=NULL) {
	RESIZE(msg,(*len),strlen(buf),buf);
    }
    fclose(fp);

    unlink(tmpfile);
    return msg;
}

char *
readln DEFUN((prompt,extra), char *prompt AND char **extra)
{
    char *ln;
    static char buf[400];
    *extra = 0;
#ifdef READLINE
    if (prompt) {
	ln = readline(prompt);
	*extra = "\n";
    } else
#else
#ifdef GETLINE
    if (prompt) {
	ln = getline(prompt);
    } else
#else
    if (prompt) {
	printf(prompt);
	fflush(stdout);
    }
#endif
#endif
	/* This is called if we have no prompt for GETLINE and READLINE,
	 * and always if neither of them are set
	 */
	{
	    if (!fgets(buf,(sizeof buf)-1,stdin))
		return 0;
	    ln = buf;
	}
    return ln;
}

char *
strip_ws DEFUN((s), char *s)
{
    char *t;
    if (!s)
	return 0;
    while (*s && isspace(*s)) s++;
    for (t = s; *t && !isspace(*t); t++) {
	/* Nothing */
    }
    if (*t)
	*t = 0;
    return s;
}
	
void
save_goof DEFUN((s), char *s)
{
    char *ln;
    char *junk;
    ln = readln("Enter filename to save to> ", &junk);
    ln = strip_ws(ln);
    if (!ln || !*ln) {
	fprintf(stderr,"goofey: Message not saved\n");
	return;
    }
    if (write_goof(s, ln)) {
	fprintf(stderr,"goofey: Could not save to '%s'\n",ln);
	return;
    }
    fprintf(stderr,"goofey: Message saved to '%s'\n",ln);
}

void
read_file DEFUN((str,len), char **str AND int *len)
{
    int pipe = 0;
    char *ln;
    FILE *f;
    char buf[100];
    char *junk;

    ln = readln("File to read from (or !command)> ",&junk);
    if (ln && *ln == '!') {
	f = popen(ln + 1, "r");
	if (!f) {
	    fprintf(stderr,"goofey: could not execute '%s'\n",ln+1);
	    return;
	}
	pipe = 1;
    } else {
	ln = strip_ws(ln);
	if (!ln || !*ln) {
	    return;
	}
	f = fopen(ln, "r");
	if (!f) {
	    fprintf(stderr,"goofey: Could not open file '%s'\n",ln);
	    return;
	}
    }
    while (fgets(buf, sizeof(buf)-1, f)!=NULL) {
	printf("%s",buf);
	RESIZE(*str,(*len),strlen(buf),buf);
    }
    if (pipe) {
	int status;
	status = pclose(f);
	if (status)
	    fprintf(stderr,"goofey: command returned error code %d\n",status);
    } else {
	fclose(f);
    }
    return;
}

void
get_recip DEFUN((recip), char **recip)
{
    char *ln,*junk;
    int len = 0;
    printf("Currently to: %s\n",(*recip)+2);
    ln = readln("Enter (comma separated) list of recipients> ",&junk);
    ln = strip_ws(ln);
    if (!ln || !*ln) {
	fprintf(stderr,"goofey: recipients not changed\n");
	return;
    }
    free(*recip);
    *recip = 0;
    RESIZE(*recip,len,2,"*s");
    RESIZE(*recip,len,strlen(ln),ln);
    RESIZE(*recip,len,1," ");
    fprintf(stderr,"goofey: recipients changed to: %s\n",ln);
}


char *
get_message DEFUN((recip), char **recip)
{
    int prompt=isatty(0);  /* Only prompt on tty's */
    char *s;
    int len;
    char *ln,*extra=0;

    s = 0;
    len = 0;

    if (prompt)
	printf("Enter your message: (blank line or ^D to end, ~h for help)\n");
    while (1) {
	int i;
	fflush(stdout);

	ln = readln(prompt?"> ":0, &extra);
	if (ln && (extra || *ln)) {
	    int meta=0;
	    int textlen;
	    textlen = i = strlen(ln);
	    if (ln[i-1] == '\n')
		textlen = i-1;
	    if (prompt && textlen == 2 && *ln=='~') {
		meta=1;
		switch(ln[1]) {
		case 'v':
		case 'e':
		    s = editmsg(s, &len);
		    break;
		case 't':
		    get_recip(recip);
		    break;
		case 'p':
		    printf("[ To: %s]\n",(*recip) + 2);
		    if (s) {
			printf("%s",s);
		    }
		    break;
		case '.':
		    textlen = 0;
		    break;
		case 'q':
		    if (write_goof(s,"dead.goofey")) {
			fprintf(stderr,
			    "goofey: Could not write to file 'dead.goofey'\n");
			break;
		    }
		    fprintf(stderr,
		    	"goofey: Message written to file 'dead.goofey'\n");
		    /* Fall through */
		case 'x':
		    fprintf(stderr,"goofey: Quitting: message not sent\n");
		    exit(0);
		case 'c':
		{
		    int leng;
		    leng = (s?strlen(s):0) + strlen(*recip) +
		    	strlen(build_arrived_string(0)) + 3;
		    fprintf(stderr,
			"goofey: Total message length is %d which %s\n", leng,
			    (leng>SERVER_MAXREQ)?
		       (autoSplit()?"will result in split": "exceeds maximum"):
			    "is OK!");
		    break;
		}
		case 'w':
		    save_goof(s);
		    break;
		case 'r':
		    read_file(&s, &len);
		    break;
		case '?':
		case 'h':
		    printf(
"~ commands for goofey message editing:\n\
~e, ~v: Edit message using editor\n\
~p:     Print current message\n\
~.:     End message (don't use this if you're using rlogin!).\n\
~w:     Write message to a file (you will be prompted for a filename)\n\
~r:     Read from a file or command.\n\
~q:     Quit, save message in dead.goofey, don't send\n\
~x:     Quit, don't send, don't save\n\
~t:     Re-enter recipients (will prompt for recipients)\n\
~c:     Check message length\n\
~h, ~?: Print help\n"
		    );
		    break;
		default:
		    fprintf(stderr,
			"goofey: Unknown ~ command. Use ~h for help\n");
		    break;
		}
	    }
	    if (prompt && (textlen==0 || (textlen==1 && *ln=='.')))
		break;          /* Exit if empty line or '.'is entered */
	    if (!meta) {
		RESIZE(s,len,i,ln);
		if (extra)
		    RESIZE(s,len,strlen(extra),extra);
	    }
	} else
	    break;
    }
    return s;
}

void
print_help VOIDFUNC
{
puts("\
Usage: goofey [-v] [options]* [command]\n\
    -v       : prints the version number\n\
    -h       : prints help.\n\
    --width=Width : set terminal width for this client\n\
    --port=PortNumber   : Specify port number where server lives.\n\
    --machine=MachineName  : Specify machine where server lives.\n\
    --no-blat : request server to send no blat message\n\
    --only-one : server will only send 1 request per connection\n\
    --return-response : server will redirect response for pages to caller\n\
    --inform-replace : will generate GOOF IN messages on client replace\n\
    --forget-fail : messages that don't get through are not buffered\n\
    --tag-success : server will add an extra character for failed messages\n\
    --format-outgoing : server will line wrap your message for you\n\
    --shutdown-broke : do not use the 'shutdown' system call\n\
\n\
The server may have further help when it is available\n\
");
}

void
check_alloc DEFUN((v,i),void * v AND int i)
{
    if (v==0) {
	fprintf(stderr,"\rGoofey: memory allocation (%d bytes) failed!\n",i);
	exit(1);
    }
}

void
do_resize DEFUN((m,l,x,s), char **m AND int *l AND int x AND char *s)
{/* Resize an block of memory */
    if (*m) 
	*m = realloc(*m,(*l)+x+2);
    else 
	*m = malloc(x+2);
    check_alloc(*m,*l + x +2);
    bcopy(s,(*m)+(*l),x);
    *l+=x;
    (*m)[*l]=0;
}

#ifdef READLINE
char *
xmalloc (int bytes)
{
    char *temp = (char *)malloc (bytes);

    check_alloc(temp,bytes);
    return (temp);
}

char *
xrealloc (char *pointer, int bytes)
{
    char *temp;

    if (!pointer)
	temp = (char *)xmalloc (bytes);
    else
	temp = (char *)realloc (pointer, bytes);

    check_alloc(temp,bytes);
    return (temp);
}
#endif