/* $Id: socklisten.c,v 1.14 2001/11/08 00:01:54 kaminsky Exp $ */

/*
 *
 * Copyright (C) 2001 Eric Peterson (ericp@lcs.mit.edu)
 * Copyright (C) 2001 Michael Kaminsky (kaminsky@lcs.mit.edu)
 *
 * This program 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.
 *
 * This program 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>

#include "rwfd.h"

#define XPREFIX "/tmp/.X11-unix/X"
#define MAXDISPLAY 99


void
usage () {
  fprintf(stderr, "invalid argument:\nusage: socklisten -x | -u <path> |"
	          " <tcpport num>\n");
  exit (-1);
}


/* must be larger than sockaddr_un.sun_path */
char *path2unlink = NULL;

int s;  /*listening socket*/

void
unlinkpath () {
  close (s);
  if (path2unlink)
    unlink (path2unlink);
}

void exitzero (int n)
{
  unlinkpath ();
  exit (0);
}

int main(int argc, char **argv)
{
  int fd; /*accepted socket*/
  int domainsock_mode;
  u_int16_t tcplistenport;
  int reslen;
  char *unixpath = NULL;
  
  struct sockaddr_un bind_addr_un;
  struct sockaddr_in bind_addr_in;

  struct sockaddr *pfrom;
  size_t pfromlen;
  struct sockaddr_un from_un;
  struct sockaddr_in from_in;

  /* it appears SIGTERM default handler doesn't call exit, strange */
  signal (SIGTERM, exitzero);
  
  if (argc < 2 || argc > 3)
    usage ();

  if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'x')
    domainsock_mode = 1;
  else if (argc == 3) {
    domainsock_mode = 1;
    unixpath = argv[2];
  }
  else {
    if (sscanf (argv[1], "%hu", &tcplistenport) != 1)
      usage();
    domainsock_mode = 0;
  }


  if (!domainsock_mode) {
    struct hostent *localhost;
    pfromlen = sizeof (struct sockaddr_in);
    pfrom = (struct sockaddr *) &from_in;
    
    if (!(localhost = gethostbyname ("localhost")))
      perror ("localhost namelookup failure ");

    bzero (&bind_addr_in, sizeof (struct sockaddr_in));
    bind_addr_in.sin_family = AF_INET;
    bind_addr_in.sin_port = htons (tcplistenport);
    bind_addr_in.sin_addr = *(struct in_addr *) localhost->h_addr;

    s = socket (AF_INET, SOCK_STREAM, 0);
    if (bind (s, (struct sockaddr *) &bind_addr_in,
	      sizeof (bind_addr_in)) < 0) {
      perror ("bind");
      close(s);
      return -1;
    }

    write (0, "", 1);
  }
  else {
    pfromlen = sizeof (struct sockaddr_un);
    pfrom = (struct sockaddr *)&from_un;
    
    s = socket (AF_UNIX, SOCK_STREAM, 0);

    if (unixpath) {
      unlink (unixpath);
      bzero ((char *) &bind_addr_un, sizeof (bind_addr_un));
      bind_addr_un.sun_family = AF_UNIX;
      strcpy (bind_addr_un.sun_path, unixpath);

      path2unlink = malloc (strlen (unixpath) + 1);
      if (!path2unlink) {
	fprintf (stderr, "malloc failed: %s\n", strerror (errno));
	return -1;
      }
      
      strcpy (path2unlink, unixpath);
      if (atexit (unlinkpath) != 0) {
	fprintf (stderr, "atexit registration failed\n");
	abort ();
      }
      
      if (bind (s,
		(struct sockaddr *) &bind_addr_un,
		sizeof (bind_addr_un)) < 0) {
	perror("bind to unix domain socket ");
	return -1;
      }
    }
    else {
      /*** x forwarding ***/

      /* start with :2 to leave :{0,1} for X server */
      int screen_num = 1;
      int bindretval;
      
      bzero ((char *) &bind_addr_un, sizeof (bind_addr_un));
      bind_addr_un.sun_family = AF_UNIX;
      
      unixpath = bind_addr_un.sun_path;
      strcpy (unixpath, XPREFIX);
      
      do {
	if (screen_num >= MAXDISPLAY) {
	  fprintf (stderr, "failed to bind to UNIX domain socket in %s\n", 
		   XPREFIX);
	  return -1;
	}
	sprintf (unixpath + sizeof (XPREFIX) - 1, "%d", ++screen_num);
      }
      while ((bindretval = bind (s, (struct sockaddr *) &bind_addr_un,
				 sizeof (bind_addr_un))) < 0
	     && errno == EADDRINUSE);

      if (bindretval < 0) {
	fprintf (stderr, "failed to bind to UNIX domain socket in %s\n%s\n",
		 XPREFIX, strerror (errno));
	return -1;
      }

      path2unlink = malloc (strlen (unixpath) + 1);
      if (!path2unlink) {
	fprintf (stderr, "malloc failed: %s\n", strerror (errno));
	return -1;
      }
      
      strcpy (path2unlink, unixpath);
      if (atexit (unlinkpath) != 0) {
	fprintf (stderr, "atexit registration failed\n");
	return -1;
      }

      /* rex client needs this so it can setenv DISPLAY */
      printf (":%d\n", screen_num);
      fflush (stdout);
    }
  }

  
  if (listen(s, 5) < 0) {
    perror("socklisten:   listen");
    close(s);
    return -1;
  }

  reslen = pfromlen;

  while((fd = accept(s, pfrom, &reslen)) >= 0) {
    reslen = pfromlen;
    
    fprintf(stderr, "socklisten:   accepting connection\n");
    if (writefd(0, "", 1, fd) < 0) {
      fprintf(stderr, "socklisten:   failed to send proxy accepted fd\n");
      return -1;
    }
    close(fd);
  }

  fprintf (stderr, "socklisten:  exiting\n");
  return 0;
}

