RCS_ID_C="$Id: inetd.c,v 3.4 1994/05/02 19:43:59 jraja Exp $";
/*
 * inetd.c --- Internet super-server
 *
 * Author: ppessi <Pekka.Pessi@hut.fi>
 *
 * Copyright © 1993 AmiTCP/IP Group, <amitcp-group@hut.fi>
 *                  Helsinki University of Technology, Finland.
 *                  All rights reserved.
 *
 * Created      : Mon May 24 00:34:11 1993 ppessi
 * Last modified: Sat Feb 26 05:54:05 1994 ppessi
 *
 * $Log: inetd.c,v $
 * Revision 3.4  1994/05/02  19:43:59  jraja
 * Changed exitcode() to return 0 for concistency with other progs.
 * Removed inclusion of the (removed) inetdlib.h.
 *
 * Revision 3.3  1994/04/25  14:51:19  jraja
 * Fixed server argument parsing bugs.
 *
 * Revision 3.2  1994/02/26  09:23:56  ppessi
 * Fixed SAS C 6.51 incompatibilities.
 * Fixed bug in timer handling
 *
 * Revision 3.1  1994/02/24  03:35:35  ppessi
 * Changed the VERSTAG and copyright formats slightly
 *
 * Revision 1.11  1993/10/21  01:51:20  ppessi
 * Added correct prototype for CheckIO()
 *
 * Revision 1.10  1993/10/20  05:31:07  ppessi
 * Using now gettimeofday().
 *
 * Revision 1.1  93/05/24  21:42:36  ppessi
 * Original BSD code.
 */

#include "inetd_rev.h"
static const char version[] = VERSTAG " "
     "Copyright © 1993 AmiTCP/IP Group, <amitcp-group@hut.fi>\n"
                      "Helsinki University of Technology, Finland.\n"
"@(#) Copyright © 1983 The Regents of the University of California.\n"
                      "All rights reserved.\n";

/*
 * Copyright (c) 1983,1991 The Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/****** netutil.doc/inetd ***************************************************
*
*  NAME
*        inetd - internet ``super-server''
*
*  TEMPLATE
*       inetd DEBUG/S CONFIGFILE
*
*  DESCRIPTION
*
*       Inetd should be run when the AmiTCP/IP protocol stack is started.
*       Inetd listens for connections on certain internet sockets.  When a
*       connection is found on one of its sockets, it decides what service the
*       socket corresponds to, and invokes a program to service the request.
*       After the program is finished, it continues to listen on the socket
*       (except in some cases which will be described below).  Essentially,
*       inetd allows running one daemon to invoke several others, reducing
*       load on the system.
*
*  PARAMETERS
*       DEBUG      Turns on debugging.
*
*       CONFIGFILE Specifies the configuration file name.
*
*  CONFIGURATION
*
*       Upon execution, inetd reads its configuration information from a
*       configuration file which, by default, is AmiTCP:db/inetd.conf. There
*       must be an entry for each field of the configuration file, with
*       entries for each field separated by a tab or a space.  Comments are
*       denoted by a ``#'' at the beginning of a line or ``;'' anywhere in the
*       line. There must be an entry for each field. The fields of the
*       configuration file are as follows:
*
*             service name
*             socket type
*             protocol
*             wait/nowait
*             user
*             server program
*             server program name
*             server program arguments
*
*       The service-name entry is the name of a valid service in the
*       netdatabase.  For ``internal'' services (discussed below), the service
*       name must be the official name of the service.
*
*       The socket-type should be one of ``stream'', ``dgram'', ``raw'',
*       ``rdm'', or ``seqpacket'', depending on whether the socket is a
*       stream, datagram, raw, reliably delivered message, or sequenced packet
*       socket. Current system supports only stream, datagram and raw
*       protocols.
*
*       The protocol must be a valid protocol as given in netdatabase.
*       Examples might be ``tcp'' or ``udp''.
*
*       The wait/nowait entry is useful for datagram sockets only (other
*       sockets should have a ``nowait'' entry in this space).  If a datagram
*       server connects to its peer, freeing the socket so inetd can received
*       further messages on the socket, it is said to be a ``multi-threaded''
*       server, and should use the ``nowait'' entry.  For datagram servers
*       which process all incoming datagrams on a socket and eventually time
*       out, the server is said to be ``single-threaded'' and should use a
*       ``wait'' entry.  Comsat and talkd are both examples of the latter type
*       of datagram server.
*
*       The user entry should contain the user name of the user as whom the
*       server should run. This field is for Unix and future compability
*       only.
*
*       The server-program entry should contain the pathname of the program
*       which is to be executed by inetd when a request is found on its
*       socket.  If the server program is resident, the path name should be
*       suppressed. If inetd provides this service internally, this entry
*       should be ``internal''.
*
*       The server-program-name is CLI command name for the server process. It
*       is shown in the printout of ``status'' command. (Task name of the
*       server process is the service and the peer address, e.g. ``echo
*       [192.233.15.19]''.) This and argument entry are optional.
*
*       The server program arguments should be just as arguments normally are.
*
*       Inetd provides several ``trivial'' services internally by use of
*       routines within itself.  These services are ``echo'', ``discard'',
*       ``chargen'' (character generator), ``daytime'' (human readable time),
*       and ``time'' (machine readable time, in the form of the number of
*       seconds since mid­ night, January 1, 1900).  All of these services are
*       TCP and UDP based.  For details of these services, consult the
*       appropriate RFC from the Network Information Center.
*
*       Inetd rereads its configuration file when it receives the CTRL-F
*       signal.  Services may be added, deleted or modified when the
*       configuration file is reread.
*
*  HISTORY
*       The inetd command appeared in 4.3BSD system.
*
*  SEE ALSO
*
*****************************************************************************
*
*/

/*
 * Inetd - Internet super-server
 *
 * This program invokes all internet services as needed.
 * connection-oriented services are invoked each time a connection is
 * made, by creating a process. Created server process is passed the
 * connection as file descriptor 0 and is expected to do a getpeername
 * to find out the source host and port.
 *
 * Datagram oriented services are invoked when a datagram arrives; a
 * process is created and passed a pending message on file descriptor
 * 0. Datagram servers may either connect to their peer, freeing up
 * the original socket for inetd to receive further messages on, or
 * ``take over the socket'', processing all arriving datagrams and,
 * eventually, timing out. The first type of server is said to
 * be ``multi-threaded''; the second type of server
 * ``single-threaded''.
 *
 * Inetd uses a configuration file which is read at startup and,
 * possibly, at some later time in response to a signal CTRL_F.  The
 * configuration file is parsed with standard DOS functions with
 * template given below. Multiple line entries must have a ``+'' char at
 * the end of the file.
 *
 * 	service 			must be in /etc/services
 *	socket type			stream/dgram/raw/rdm/seqpacket
 *	protocol			must be in /etc/protocols
 *	dowait			        single-threaded/multi-threaded
 *	user				user to run daemon as
 *	server program			full path name
 *	server program name		CLI command name (optional)
 *	server program arguments	Normal command argument line 
 *
 * Comment lines are indicated by a `#' in column 1. 
 */

#include <sys/param.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/time.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <errno.h>
#include <signal.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <stdarg.h>
#include <syslog.h>

#ifdef AMIGA
#include <exec/execbase.h>
#include <exec/memory.h>
#include <devices/timer.h>
#include <dos/dostags.h>
extern struct ExecBase *SysBase;
#if __SASC
#include <proto/socket.h>
#include <proto/dos.h>
#include <clib/exec_protos.h>
#include <pragmas/exec_sysbase_pragmas.h>
#include <pragmas/timer_pragmas.h>
/* #elif __GNUC__
#include <inline/socket.h>
#include <inline/exec.h>
*/
#else 
#error Unsupported compiler
#include <clib/socket_protos.h>
#endif

/* Correct prototype for the CheckIO.
 * (The one in clib/exec_protos.h has wrong return value type: BOOL (16 bits) 
 * instead of a pointer (32 bits)!)
 */
struct IORequest * CheckIO(struct IORequest *req);
#else /* def AMIGA */
#include <pwd.h>
#include <sys/file.h>
#include <sys/wait.h>
#include <sys/resource.h>
#endif

#include "inetd.h"

/* don't start more than TOOMANY servers in CNT_INTVL seconds */
#define	TOOMANY		10
#define	CNT_INTVL	60		

#define	RETRYTIME	(60*10)		/* retry after bind or server fail */

#define INETDNAME "inetd"

#define MAXARGS 8
enum fileargs 
{ 
  service = 0, socket_type, protocol, wait, user, server, cmdname, arguments
};
#define TYPETEMPLATE "stream,dgram,raw,rdm,seqpacket"
#define WAITTEMPLATE "nowait,wait"

void reapchild(void);
void startserver(register struct servtab *sep, long ctrl);
void askalarm(long seconds);
void getalarm(void);
void config(void);
void retry(void);
void setup(register struct servtab *sep);
struct servtab *enter(struct servtab *cp);

int setconfig(void);
struct servtab *getconfigent(void);
void freeconfig(register struct servtab *cp);
void endconfig(void);
void print_service(char *action, struct servtab *sep);

ASM LONG exitcode(REG(d0) LONG status, REG(d1) LONG exitmessage);
LONG start_builtin(void);
void setproctitle(char *a, int s);

UBYTE *proctitle(char *a, int s);
void  amigainit(void);
void amigadeinit(void);
static ULONG csprintf(struct CSource *buf, const char *fmt, ...);

int	nsock = 0, maxsock = 0;
fd_set	allsock;
int	options = 0;
struct	servent *sp = NULL;

/* 
 * We create a "process table" for builtins, because they 
 * must be waited as they use the same code segment as we do 
 */
#define MAX_BUILTIN_PROC 16
int builtins_started = 0;
long ptable[MAX_BUILTIN_PROC] = { 0 };

struct DaemonPort *dport = NULL;

#define DO_WAIT   ((struct DaemonMessage *)1)
#define DONT_WAIT ((struct DaemonMessage *)NULL)

struct MsgPort *tport = NULL;
struct timerequest *tmsg = NULL;
int   timer_is_open = 0;
#define TimerBase (tmsg->tr_node.io_Device)

struct	servtab {
	char	*se_service;		/* name of service */
	long	se_socktype;		/* type of socket to use */
	char	*se_proto;		/* protocol used */
	struct 	DaemonMessage *se_wait; /* single threaded server */
	char	*se_user;		/* user name to run as */
	struct	biltin *se_bi;		/* if built-in, description */
	char	*se_server;		/* server program */
	char	*se_argv0;	        /* cli name for daemon */
	char	*se_argv;	        /* program arguments */
	long	se_fd;			/* open descriptor */
	struct	sockaddr_in se_ctrladdr;/* bound address */
	struct	timeval se_time;	/* start of se_count */
	struct	servtab *se_next;
	short	se_checked;		/* looked at during merge */
	short	se_count;		/* number started since se_time */
} *servtab;

/* 
 * Internal Services 
 *
 * Because Data Segment is *not* duplicated by CreateNewProc 
 * server routines must be re-entrable and not touch global data
 */ 
int echo_stream(void *SocketBase, int s, struct servtab *sep);
int echo_dg(void *SocketBase, int s, struct servtab *sep);
int discard_stream(void *SocketBase, int s, struct servtab *sep);
int discard_dg(void *SocketBase, int s, struct servtab *sep); 
int machtime_stream(void *SocketBase, int s, struct servtab *sep);
int machtime_dg(void *SocketBase, int s, struct servtab *sep); 
int daytime_stream(void *SocketBase, int s, struct servtab *sep);
int daytime_dg(void *SocketBase, int s, struct servtab *sep);
int chargen_stream(void *SocketBase, int s, struct servtab *sep);
int chargen_dg(void *SocketBase, int s, struct servtab *sep);

struct biltin {
	char	*bi_service;		/* internally provided service name */
	int	bi_socktype;		/* type of socket supported */
	int	bi_process;		/* 1 if should start a process */
	struct	DaemonMessage *bi_wait;	/* DO_WAIT if should wait for child */
	int	(*bi_fn)(void *SocketBase, int s, struct servtab *sep);
				        /* function which performs it */
} biltins[] = {
	/* Echo received data */
	"echo",		SOCK_STREAM,	1, DONT_WAIT,	echo_stream,
	"echo",		SOCK_DGRAM,	0, DONT_WAIT,	echo_dg,

	/* Internet /dev/null */
	"discard",	SOCK_STREAM,	1, DONT_WAIT,	discard_stream,
	"discard",	SOCK_DGRAM,	0, DONT_WAIT,	discard_dg,

	/* Return 32 bit time since 1970 */
	"time",		SOCK_STREAM,	0, DONT_WAIT,	machtime_stream,
	"time",		SOCK_DGRAM,	0, DONT_WAIT,	machtime_dg,

	/* Return human-readable time */
	"daytime",	SOCK_STREAM,	0, DONT_WAIT,	daytime_stream,
	"daytime",	SOCK_DGRAM,	0, DONT_WAIT,	daytime_dg,

	/* Familiar character generator */
	"chargen",	SOCK_STREAM,	1, DONT_WAIT,	chargen_stream,
	"chargen",	SOCK_DGRAM,	0, DONT_WAIT,	chargen_dg,
	0
};

/* Internal buffer size. Allocated from stack */
#define	BUFSIZE	8192

#define NUMINT	(sizeof(intab) / sizeof(struct inent))

int debug;
char *CONFIG = _PATH_INETDCONF;

main(void)
{
  register struct servtab *sep;
  ULONG events;
  ULONG mask;

  {
    LONG Argv[2];
    struct RDArgs *rdargs;

    Argv[0] = Argv[1] = 0;
    rdargs = ReadArgs("DEBUG/S,CONFIGFILE", Argv, NULL);

    if (!rdargs) {
      PrintFault(IoErr(), "ReadArgs");
      exit(1);
    }
    debug = Argv[0];
    if (Argv[1])
      CONFIG = strdup((char *)Argv[1]);

    FreeArgs(rdargs);
  }

  if (debug) {
    options |= SO_DEBUG;
  } else {
#if 0
    daemon(0, 0);
#endif
  }

  openlog("inetd", LOG_PID | LOG_NOWAIT, LOG_DAEMON);

  amigainit();

  events = 1 << (dport->dp_Port.mp_SigBit) | 1 << (tport->mp_SigBit) 
    | SIGBREAKF_CTRL_F | SIGBREAKF_CTRL_C;
  SetSocketSignals(0L, 0L, 0L); /* no no no no no no there is no EINTR */

  config();

  for (;;) {
    int n, ctrl;
    fd_set readable;

    mask = events;
    readable = allsock;
    if ((n = WaitSelect(maxsock + 1, &readable, (fd_set *)NULL,
		    (fd_set *)NULL, (struct timeval *)NULL, &mask)) < 0) {
      if (errno != EINTR)
	syslog(LOG_WARNING, "select: %s", strerror(errno));
      continue;
    }

    if (mask & SIGBREAKF_CTRL_F) {
      config();
    }

    if (mask & SIGBREAKF_CTRL_C) {
      exit(0);
    }

    if (mask & (1<<(tport->mp_SigBit))) {
      getalarm();
    }

    if (mask & (1<<(dport->dp_Port.mp_SigBit))) {
      reapchild();
    }

    for (sep = servtab; n && sep; sep = sep->se_next)
      if (sep->se_fd != -1 && FD_ISSET(sep->se_fd, &readable)) {
	n--;
	if (debug)
	  fprintf(stderr, "someone wants %s\n", sep->se_service);
	if (sep->se_socktype == SOCK_STREAM) {
	  ctrl = accept(sep->se_fd, NULL, NULL);
	  if (debug)
	    fprintf(stderr, "accept, ctrl %ld\n", ctrl);
	  if (ctrl < 0) {
	    if (errno != EINTR)
	      syslog(LOG_WARNING, "accept (for %s): %s", 
		     sep->se_service, strerror(errno));
	    continue;
	  }
	} else
	  ctrl = sep->se_fd;
	if (sep->se_bi == 0 || sep->se_bi->bi_process) {
	  startserver(sep, ctrl);
	} else {
	  (*sep->se_bi->bi_fn)(SocketBase, ctrl, sep);
	  if (sep->se_socktype == SOCK_STREAM)
	    CloseSocket(ctrl);
	}
      }
  }
}

/*
 * startserver:
 *      Launch a new process to handle service
 */
void 
startserver( register struct servtab *sep, long ctrl)
{
  LONG id;
  struct DaemonMessage *dm = CreateIORequest(dport, sizeof(*dm));
  UBYTE *dname = proctitle(sep->se_service, ctrl);
  int ok = 0;

  if (!dm || !dname) { 
    syslog(LOG_ERR, "inetd exhausted memory, exits");
    if (dm) DeleteIORequest(dm);
    if (dname) FreeVec(dname);
    exit(1);
  }

  if (sep->se_count++ == 0) {
    (void)gettimeofday(&sep->se_time, (struct timezone *)0);
  } else if (sep->se_count >= TOOMANY) {
    struct timeval now;
    (void)gettimeofday(&now, (struct timezone *)0);
    if (now.tv_sec - sep->se_time.tv_sec > CNT_INTVL) {
      sep->se_time = now;
      sep->se_count = 1;
    } else {
      syslog(LOG_ERR, "%s/%s server failing (looping), "
	     "service terminated\n", sep->se_service, sep->se_proto);
      FD_CLR(sep->se_fd, &allsock);
      (void) CloseSocket(sep->se_fd);
      sep->se_fd = -1;
      sep->se_count = 0;
      nsock--;
      askalarm(RETRYTIME);
    }
  }

  if (sep->se_socktype == SOCK_STREAM)
    id = ReleaseSocket(ctrl, -1);
  else
    id = ReleaseCopyOfSocket(ctrl, -1);

  if (dm != NULL && id != -1) {
    dm->dm_Msg.mn_Node.ln_Name = dname;
    dm->dm_Id = id;
    dm->dm_Family = AF_INET;
    dm->dm_Type = sep->se_socktype;

    if (sep->se_bi) {
      struct Process *pid = NULL;
      /* 
       * Start a built in function
       */
      dm->dm_Retval = (LONG)sep;
      dm->dm_Type = DMTYPE_INTERNAL;
      if (builtins_started < MAX_BUILTIN_PROC) {
	pid =
	  CreateNewProcTags(NP_Entry, start_builtin, NP_StackSize, BUFSIZE*2,
			    NP_ExitCode, exitcode, NP_ExitData, dm,
			    NP_Priority, -10,
			    dname ? NP_Name : TAG_DONE, dname,
			    TAG_DONE);
	ok = pid != NULL;
	if (ok) {
	  dm->dm_Pid = pid; 
	  ptable[builtins_started++] = (long)pid;
	}
      }
    } else {
      /*
       * Start an external process
       */
      BPTR input = Open("NIL:", MODE_OLDFILE);
      BPTR output = Open("NIL:", MODE_OLDFILE);
#if 1
      BPTR seglist;
      struct Segment *sl;

      /* Try to find seglist from resident list */
      Forbid();
      if (sl = FindSegment(sep->se_server, NULL, FALSE)) {
	if (sl->seg_UC >= 0) {
	  sl->seg_UC++;
	  dm->dm_Seg = sl;		/* must be freed */
	} else if (sl->seg_UC != -2) {
	  sl = NULL;
	}
      }
      Permit();

      if (sl)
	seglist = sl->seg_Seg;
      else
	seglist = NewLoadSegTags(sep->se_server, TAG_DONE);

      if (input && output && seglist) {
	ok = (int)
	  CreateNewProcTags(NP_Seglist, seglist,
			    NP_FreeSeglist, sl == NULL,
			    NP_Cli, TRUE, 
			    NP_CommandName, sep->se_argv0,
			    NP_Arguments, sep->se_argv, 
			    NP_Input, input, 
			    NP_Output, output,
			    NP_StackSize, BUFSIZE*2, 
			    NP_Priority, -10, 
			    NP_ExitCode, dport->dp_ExitCode,
			    NP_ExitData, dm, 
			    dname ? NP_Name : TAG_END, dname,
			    TAG_END, NULL);
      }
      if (!ok) {
	if (seglist) UnLoadSeg(seglist);
	if (input) Close(input);
	if (output) Close(output);
      }
    }
#else
      if (input && output) {
	ok = !SystemTags(sep->se_argv,
			 SYS_Asynch, 1, SYS_UserShell, 1,
			 SYS_Input, input, SYS_Output, output,
			 NP_StackSize, BUFSIZE*2,
			 NP_ExitCode, exitcode, NP_ExitData, dm,
			 NP_Priority, -10, 
			 dname ? NP_Name : TAG_END, dname,
			 TAG_END, NULL);
      }
      if (!ok) {
	if (input) Close(input);
	if (output) Close(output);
      }
    }
#endif
    if (ok && sep->se_wait) {
      sep->se_wait = dm;
      if (sep->se_fd >= 0) {
	FD_CLR(sep->se_fd, &allsock);
	nsock--;
      }
    }
  }

  /* Clean up */
  if (!ok) {
    /* Drop the pending datagram */
    char buf[32];
    if (sep->se_socktype != SOCK_STREAM)
      recv(ctrl, buf, sizeof (buf), 0);

    if (dm) DeleteIORequest(dm);
    if (dname) FreeVec(dname);
    if (id != -1) {
      ctrl = ObtainSocket(id, AF_INET, sep->se_socktype, 0);
      if (ctrl >= 0) CloseSocket(ctrl);
    }
  }
}

/*
 * reapchild: 
 *      handle death messages from childs
 */
void
reapchild(void)
{
  struct DaemonMessage *dm;

  while (dm = (struct DaemonMessage *)GetMsg(dport)) {
    long status = dm->dm_Retval;
    register struct servtab *sep;

    if (debug)
      fprintf(stderr, "reaped child %s\n", dm->dm_Msg.mn_Node.ln_Name);
    for (sep = servtab; sep; sep = sep->se_next)
      if (sep->se_wait == dm) {
	if (status)
	  syslog(LOG_WARNING,
		 "%s: exit status 0x%lx",
		 sep->se_server, status);
	if (debug)
	  fprintf(stderr, "restored %s, fd %ld\n",
		  sep->se_service, sep->se_fd);
	FD_SET(sep->se_fd, &allsock);
	nsock++;
	sep->se_wait = DO_WAIT;
      }

    /* update "process table" */ 
    if (dm->dm_Type == DMTYPE_INTERNAL) {
      long pid = (long)dm->dm_Pid;
      int i = --builtins_started;
      for (i; i >= 0; i--) 
	if (pid = ptable[i]) {
	  ptable[i] = ptable[builtins_started];
	  break;
	}
      if (i < 0) {
	syslog(LOG_ALERT, "inetd: unknown internal daemon 0x%lx", pid);
	builtins_started++;
      }
    }
    /* Free resources allocated for the daemon */
    if (dm->dm_Seg) {
      Forbid();
      dm->dm_Seg->seg_UC--;
      Permit();
    }
    if (dm->dm_Msg.mn_Node.ln_Name)
      FreeVec(dm->dm_Msg.mn_Node.ln_Name);
    DeleteIORequest(dm);
  }
}

/*
 * config:
 *      configure services
 */
void
config(void)
{
  register struct servtab *sep, *cp, **sepp;

  if (!setconfig()) {
    endconfig();
    return;
  }

  for (sep = servtab; sep; sep = sep->se_next)
    sep->se_checked = 0;
  while (cp = getconfigent()) {
    for (sep = servtab; sep; sep = sep->se_next)
      if (strcmp(sep->se_service, cp->se_service) == 0 &&
	  strcmp(sep->se_proto, cp->se_proto) == 0)
	break;
    if (sep != 0) {
      freeconfig(sep);
      /*
       * sep->se_wait may be holding the "pid" of a daemon
       * that we're waiting for.  If so, don't overwrite
       * it unless the config file explicitly says don't 
       * wait.
       */
      if (cp->se_bi == 0 && 
	  (sep->se_wait == DO_WAIT || cp->se_wait == DONT_WAIT))
	sep->se_wait  = cp->se_wait;
      sep->se_service = cp->se_service;
      sep->se_proto   = cp->se_proto;
      sep->se_user    = cp->se_user;
      sep->se_server  = cp->se_server;
      sep->se_argv    = cp->se_argv;
      if (debug)
	print_service("REDO", sep);
    } else {
      sep = enter(cp);
      if (debug)
	print_service("ADD ", sep);
    }
    sep->se_checked = 1;
    sp = getservbyname(sep->se_service, sep->se_proto);
    if (sp == 0) {
      syslog(LOG_ERR, "%s/%s: unknown service",
	     sep->se_service, sep->se_proto);
      if (sep->se_fd != -1)
	(void) CloseSocket(sep->se_fd);
      sep->se_fd = -1;
      continue;
    }
    if (sp->s_port != sep->se_ctrladdr.sin_port) {
      sep->se_ctrladdr.sin_port = sp->s_port;
      if (sep->se_fd != -1)
	(void) CloseSocket(sep->se_fd);
      sep->se_fd = -1;
    }
    if (sep->se_fd == -1)
      setup(sep);
  }
  endconfig();
  /*
   * Purge anything not looked at above.
   */
  sepp = &servtab;
  while (sep = *sepp) {
    if (sep->se_checked) {
      sepp = &sep->se_next;
      continue;
    }
    *sepp = sep->se_next;
    if (sep->se_fd != -1) {
      FD_CLR(sep->se_fd, &allsock);
      nsock--;
      (void) CloseSocket(sep->se_fd);
    }
    if (debug)
      print_service("FREE", sep);
    freeconfig(sep);
    free((char *)sep);
  }
}

/*
 * askalarm:
 *      ask for timeout
 */
void 
askalarm(long seconds) 
{
  if (tmsg->tr_node.io_Message.mn_Node.ln_Type == NT_UNKNOWN 
      || CheckIO(tmsg)) {
    tmsg->tr_time.tv_secs = seconds;
    tmsg->tr_time.tv_micro = 0;
    SendIO(tmsg);
  }
}

/*
 * getalarm:
 *      handle timeouts
 */
void 
getalarm(void) 
{
  struct Message *msg;
  while (msg = GetMsg(tport)) {
    if ((struct timerequest *)msg == tmsg) {
      tmsg->tr_node.io_Message.mn_Node.ln_Type = NT_UNKNOWN; 
      retry();
      continue;
    }
  }
}

/*
 * retry:
 *      try to setup all configured services
 */
void
retry(void)
{
  register struct servtab *sep;

  for (sep = servtab; sep; sep = sep->se_next)
    if (sep->se_fd == -1)
      setup(sep);
}

/*
 * setup:
 *      open a socket for given port
 */
void
setup(register struct servtab *sep)
{
  int on = 1;

  if ((sep->se_fd = socket(AF_INET, sep->se_socktype, 0)) < 0) {
    syslog(LOG_ERR, "%s/%s: socket: %s",
	   sep->se_service, sep->se_proto, strerror(errno));
    return;
  }
#define	turnon(fd, opt) \
  setsockopt(fd, SOL_SOCKET, opt, (char *)&on, sizeof (on))
    if (strcmp(sep->se_proto, "tcp") == 0 && (options & SO_DEBUG) &&
	turnon(sep->se_fd, SO_DEBUG) < 0)
      syslog(LOG_ERR, "setsockopt (SO_DEBUG): %s", strerror(errno));
  if (turnon(sep->se_fd, SO_REUSEADDR) < 0)
    syslog(LOG_ERR, "setsockopt (SO_REUSEADDR): %s", strerror(errno));
#undef turnon
  if (bind(sep->se_fd, (struct sockaddr *)&sep->se_ctrladdr,
	   sizeof (sep->se_ctrladdr)) < 0) {
    syslog(LOG_ERR, "%s/%s: bind: %s",
	   sep->se_service, sep->se_proto, strerror(errno));
    (void) CloseSocket(sep->se_fd);
    sep->se_fd = -1;
    askalarm(RETRYTIME);
    return;
  }
  if (sep->se_socktype == SOCK_STREAM)
    listen(sep->se_fd, 10);
  FD_SET(sep->se_fd, &allsock);
  nsock++;
  if (sep->se_fd > maxsock)
    maxsock = sep->se_fd;
}

/*
 * enter:
 *      Allocate a new server entry
 */ 
struct servtab *
enter(struct servtab *cp)
{
  register struct servtab *sep;

  sep = (struct servtab *)malloc(sizeof (*sep));
  if (sep == (struct servtab *)0) {
    syslog(LOG_ERR, "Out of memory.");
    exit(20);
  }
  *sep = *cp;
  sep->se_fd = -1;
  sep->se_next = servtab;
  servtab = sep;

  return (sep);
}

/*
 * print_service:
 *	Dump relevant information to stderr
 */
void
print_service(char *action, struct servtab *sep)
{
  fprintf(stderr, "%s: %s proto=%s, "
	  "wait=%ld, user=%s builtin=0x%lx server=%s\n",
	  action, sep->se_service, sep->se_proto,
	  sep->se_wait, sep->se_user, (int)sep->se_bi, sep->se_server);
}


/* 
 * Functions to read configuration file
 */

BPTR	fconfig = NULL;
long    linecnt = 0;
struct	servtab serv;
#define LINEBUFLEN 1024
char    *linbuf = NULL;
struct  CSource	line = { 0, 0, 0 };
struct RDArgs *largs = NULL;

void logdoserror(const char *banner);
struct CSource * nextline(BPTR fh);

int
setconfig(void)
{
  linecnt = 0;

  if (!linbuf) 
    linbuf = malloc(LINEBUFLEN);

  if (!largs) 
    largs = AllocDosObject(DOS_RDARGS, NULL);

  if (fconfig != NULL) {
    Seek(fconfig, 0L, OFFSET_BEGINNING);
  } else {
    fconfig = Open(CONFIG, MODE_OLDFILE);
    if (!fconfig) logdoserror("setconfig");
  }
  return (fconfig != NULL && linbuf != NULL && largs != NULL);
}

void
endconfig(void)
{
  if (fconfig) {
    (void) Close(fconfig);
    fconfig = NULL;
  }
#if 0
  if (linbuf) {
    free(linbuf);
    linbuf = NULL;
  }
#endif
  if (largs) {
    FreeDosObject(DOS_RDARGS, largs);
    largs = NULL;
  }
}

struct servtab *
getconfigent(void)
{
  register struct servtab *sep = &serv;
  struct CSource *cp;

 more:
  while ((cp = nextline(fconfig)) && 
	 (cp->CS_Buffer[0] == '#' || cp->CS_Buffer[0] == ';'))
    ;
  if (cp == NULL)
    return NULL;

  {
    short len, i, item;
    char *old, *new; 
    UBYTE *argv[MAXARGS];
    UBYTE argbuf[LINEBUFLEN];

    /* Parse line */
    for (len = i = 0; i < MAXARGS - 1; i++) {
      argv[i] = argbuf + len;
      item = (short)ReadItem(argbuf + len, sizeof(argbuf) - len, cp);
      if (item < 0) {
	logdoserror("ReadItem");
	return NULL;
      } else if (item == ITEM_NOTHING) {
	for (; i < MAXARGS - 1; i++) {
	  argv[i] = " ";
	  len += 2;
	}
	break;
      } else {
	len += strlen(argv[i]) + 1;
      }
    }
    if (i == MAXARGS - 1) { /* arguments to the server */
      argv[i] = cp->CS_Buffer + cp->CS_CurChr;
      len += strlen(argv[i]) + 1;
    }

    sep->se_socktype = FindArg(TYPETEMPLATE, (STRPTR)argv[socket_type]) + 1;
    if (sep->se_socktype == 0) 
      sep->se_socktype = DMTYPE_UNKNOWN;

    switch (FindArg(WAITTEMPLATE, (STRPTR)argv[wait])) {
    case -1:
      /* A error message might be cool */
    case 0:
      sep->se_wait = DONT_WAIT;
      break;
    case 1:
      sep->se_wait = DO_WAIT;
      break;
    }

    new = malloc(len);
    if (!new) {
      SetIoErr(ERROR_NO_FREE_STORE);
      logdoserror("getconfigent");
      return NULL;
    }

    sep->se_service = new; 
    old = (char *)argv[service]; while (*new++ = *old++); 
    sep->se_proto = new;
    old = (char *)argv[protocol]; while (*new++ = *old++); 
    sep->se_user = new;
    old = (char *)argv[user]; while (*new++ = *old++); 
    sep->se_server = new;
    old = (char *)argv[server]; while (*new++ = *old++); 
    sep->se_argv0 = new;
    old = (char *)argv[cmdname]; while (*new++ = *old++); 
    sep->se_argv = new;
    old = (char *)argv[arguments]; while (*new++ = *old++); 
  }
  
  if (strcmp(sep->se_server, "internal") == 0) {
    register struct biltin *bi;

    for (bi = biltins; bi->bi_service; bi++)
      if (bi->bi_socktype == sep->se_socktype &&
	  strcmp(bi->bi_service, sep->se_service) == 0)
	break;
    if (bi->bi_service == 0) {
      syslog(LOG_ERR, "inetd: internal service %s unknown\n", sep->se_service);
      free(sep->se_service);
      goto more;
    }
    sep->se_bi = bi;
    sep->se_wait = bi->bi_wait;
  } else
    sep->se_bi = NULL;

  return (sep);
}

void
freeconfig(register struct servtab *cp)
{
  if (cp->se_service)
    free(cp->se_service);
}

/*
 * nextline
 *      get a CSource string containing next line
 */
struct CSource *
nextline(BPTR fh)
{
  size_t len = line.CS_Length = 0;
  UBYTE *buffer = line.CS_Buffer = linbuf;
  
  line.CS_CurChr = 0;
  
  for (;;) {
    linecnt++;
    if (FGets(fh, buffer + len, LINEBUFLEN - len - 1) == NULL) {
      if (IoErr()) {
	logdoserror("nextline");
	return NULL;
      }
      if (len == 0)
	return NULL;
    }

    len += strlen(buffer + len);

    /* a long line ? */
    if (len == LINEBUFLEN - 1 && buffer[LINEBUFLEN - 2] != '\n') {
      if (buffer[0] != '#') {
	SetIoErr(ERROR_LINE_TOO_LONG);
	logdoserror("nextline");
	return NULL;
      } else {
	/* A kludge for LONG comment lines */
	len = 1;
	linecnt--;
	continue;
      }
    }

    /* Check for a continued line */
    if (len > 1 && buffer[len - 2] != '+') {
      break;
    } else {
      /* current line continues */
      if (len > 2)
	len -= 2;
      else 
	len = 0;
    }
  }
  
  /*
   * Ensure that the line ends with '\n'.
   * This is required by AmigaDOS parsing functions.
   * There must be space for the newline, if there wouldn't,
   * the "long line" would have been caught above.
   */
  if (buffer[len - 1] != '\n') {
    buffer[len++] = '\n';
    buffer[len] = 0;
  }
  
  line.CS_Length = len;
  return &line;
}

/* 
 * logdoserror
 *     add the last DOS error into AmiTCP/IP log
 */
void 
logdoserror(const char *banner)
{
  LONG code = IoErr();

  if (linbuf && Fault(code, (UBYTE *)banner, linbuf, LINEBUFLEN)) {
    syslog(LOG_ERR, "inetd %s at line %ld\n", linbuf, linecnt);
  } else {
    syslog(LOG_ERR, "inetd %s: unknown DOS error %ld at line %ld\n", 
	   code, linecnt);
  }
}


/*
 * Internet services provided internally by inetd:
 */

/* Echo service -- echo data back */
int
echo_stream(void *SocketBase, int s, struct servtab *sep)
{
  char buffer[BUFSIZE];
  int i;

  while ((i = recv(s, buffer, sizeof(buffer), 0)) > 0 &&
	 send(s, buffer, i, 0) > 0)
    ;
  return 0;
}

/* Echo service -- echo data back */
int
echo_dg(void *SocketBase, int s, struct servtab *sep)
{
  char buffer[BUFSIZE];
  long i, size;
  struct sockaddr sa;

  size = sizeof(sa);
  if ((i = recvfrom(s, buffer, sizeof(buffer), 0, &sa, &size)) < 0)
    return 0;
  (void) sendto(s, buffer, i, 0, &sa, sizeof(sa));

  return 0;
}

/* Discard service -- ignore data */
int
discard_stream(void *SocketBase, int s, struct servtab *sep)
{
  char buffer[BUFSIZE];

  while (recv(s, buffer, sizeof(buffer), 0) > 0)
    ;

  return 0;
}


/* Discard service -- ignore data */
int
discard_dg(void *SocketBase, int s, struct servtab *sep)
{
  char buffer[BUFSIZE];

  (void) recv(s, buffer, sizeof(buffer), 0);
  
  return 0;
}

#include <ctype.h>
#define LINESIZ 72
char ring[128];
char *endring = NULL;

void
initring(void)
{
  register int i;

  endring = ring;

  for (i = 0; i <= 128; ++i)
    if (isprint(i))
      *endring++ = i;
}

/* Character generator */
chargen_stream(void *SocketBase, int s, struct servtab *sep)
{
  register char *rs;
  long len;
  char text[LINESIZ+2];

  if (!endring) {
    initring();
  }

  text[LINESIZ] = '\r';
  text[LINESIZ + 1] = '\n';
  for (rs = ring;;) {
    if ((len = endring - rs) >= LINESIZ)
      bcopy(rs, text, LINESIZ);
    else {
      bcopy(rs, text, len);
      bcopy(ring, text + len, LINESIZ - len);
    }
    if (++rs == endring)
      rs = ring;
    if (send(s, text, sizeof(text), 0) != sizeof(text))
      break;
  }

  return 0;
}

/* Character generator */
chargen_dg(void *SocketBase, int s, struct servtab *sep)
{
  struct sockaddr sa;
  static char *rs;
  long len, size;
  char text[LINESIZ+2];

  if (endring == 0) {
    initring();
    rs = ring;
  }

  size = sizeof(sa);
  if (recvfrom(s, text, sizeof(text), 0, &sa, &size) < 0)
    return 0;

  if ((len = endring - rs) >= LINESIZ)
    bcopy(rs, text, LINESIZ);
  else {
    bcopy(rs, text, len);
    bcopy(ring, text + len, LINESIZ - len);
  }
  if (++rs == endring)
    rs = ring;
  text[LINESIZ] = '\r';
  text[LINESIZ + 1] = '\n';
  (void) sendto(s, text, sizeof(text), 0, &sa, sizeof(sa));

  return 0;
}

/*
 * Return a machine readable date and time, in the form of the
 * number of seconds since midnight, Jan 1, 1900.  Since gettimeofday
 * returns the number of seconds since midnight, Jan 1, 1970,
 * we must add 2208988800 seconds to this figure to make up for
 * some seventy years Bell Labs was asleep.
 */

long
machtime(void)
{
  struct timeval tv;

  if (gettimeofday(&tv, (struct timezone *)0) < 0) {
    /* fprintf(stderr, "Unable to get time of day\n"); */
    return (0L);
  }
  return (long)(htonl((long)tv.tv_sec + 2208988800));
}

int 
machtime_stream(void *SocketBase, int s, struct servtab *sep)
{
  long result;

  result = machtime();
  (void) send(s, (char *) &result, sizeof(result), 0);

  return 0;
}

machtime_dg(void *SocketBase, int s, struct servtab *sep)
{
  long result;
  struct sockaddr sa;
  long size;

  size = sizeof(sa);
  if (recvfrom(s, (char *)&result, sizeof(result), 0, &sa, &size) < 0)
    return 0;
  result = machtime();
  (void) sendto(s, (char *) &result, sizeof(result), 0, &sa, sizeof(sa));

  return 0;
}

/* Return human-readable time of day */
int
daytime_stream(void *SocketBase, int s, struct servtab *sep)
{
  char buffer[256];
  time_t time(), clock;
  char *ctime();
  
  clock = time((time_t *) 0);
  
  (void) strncpy(buffer, ctime(&clock), 24);   buffer[24] = '\0';
  (void) strcat(buffer, "\r\n");
  (void) send(s, buffer, strlen(buffer), 0);
  return 0;
}

/* Return human-readable time of day */
int
daytime_dg(void *SocketBase, int s, struct servtab *sep)
{
  char buffer[256];
  time_t time(), clock;
  struct sockaddr sa;
  long size;

  clock = time((time_t *) 0);

  size = sizeof(sa);
  if (recvfrom(s, buffer, sizeof(buffer), 0, &sa, &size) < 0)
    return 0;

  (void) strncpy(buffer, ctime(&clock), 24);   buffer[24] = '\0';
  (void) strcat(buffer, "\r\n");
  (void) sendto(s, buffer, strlen(buffer), 0, &sa, sizeof(sa));
  return 0;
}

/*
 * Internal server stub 
 */
LONG SAVEDS start_builtin(void)
{
  struct Process *me = (struct Process *)FindTask(0L);
  struct DaemonMessage *dm = (struct DaemonMessage *)me->pr_ExitData;
  LONG retval = DERR_LIB; 
  void *SocketBase;

  if (SocketBase = OpenLibrary("bsdsocket.library", 2L)) {
    struct servtab *sep = (struct servtab *)dm->dm_Retval;
    int s = ObtainSocket(dm->dm_Id, AF_INET, sep->se_socktype, 0);
    if (s != -1) {
      dm->dm_Id = -1;
      retval = (*sep->se_bi->bi_fn)(SocketBase, s, sep);
    } else {
      retval = DERR_OBTAIN;
    }
    CloseLibrary(SocketBase);
  } 

  return retval;
}

/* 
 * setproctitle:
 *      set the process title
 */
void
setproctitle(char *a, int s)
{
#ifdef notyet
  int size;
  register char *cp;
  struct sockaddr_in sin;
  char buf[80];
  struct CSource csbuf;
  
  csbuf.CS_Buffer = buf;
  csbuf.CS_Length = sizeof(buf) - 1;
  csbuf.CS_CurChr = 0;

  size = sizeof(sin);
  if (getpeername(s, (struct sockaddr *)&sin, &size) == 0) {
    struct MemList ml, *nml;

    (void) csprintf(csbuf, "%s [%s]", a, inet_ntoa(sin.sin_addr)); 

    ml.ml_NumEntries = 1;
    ml.ml_ME[0].me_Reqs = MEMF_PUBLIC;
    ml.ml_ME[0].me_Length = csbuf.CS_CurChr + 1;

    /* A memlist entry is needed for automatic resource deallocation */
    if ((LONG)(nml = AllocEntry(&ml)) > 0) {
      struct Task *me = FindTask(NULL);

      cp = nml->ml_ME[1].me_Addr;
      buf[csbuf.CS_CurChr] = '\0';
      strcpy(cp, buf);

      /* Old name is pointer to se_service, so there is no need to free it */
      me->tc_Node.ln_Name = cp;
      Forbid();
      AddHead(&me->tc_MemEntry, nml);
      Permit();
    }
  }
#endif
}

/* 
 * proctitle:
 *     allocate a new process title 
 */
UBYTE *
proctitle(char *a, int s)
{
  long size;
  struct sockaddr_in sin;
  register char *cp;
  char buf[80];
  struct CSource csbuf;
  
  csbuf.CS_Buffer = buf;
  csbuf.CS_Length = sizeof(buf) - 1;
  csbuf.CS_CurChr = 0;

  size = sizeof(sin);
  if (getpeername(s, (struct sockaddr *)&sin, &size) == 0) {
    (void) csprintf(&csbuf, "%s [%s]", a, inet_ntoa(sin.sin_addr)); 
  } else {
    (void) csprintf(&csbuf, "%s", a); 
  }
  if (cp = AllocVec(csbuf.CS_CurChr + 1, MEMF_PUBLIC)) {
    buf[csbuf.CS_CurChr] = '\0';
    strcpy(cp, buf);
  }
  return cp;
}

#if __SASC
#define SysBase (*(struct ExecBase**)4)
/* 
 * Amiga process catcher
 */
ASM LONG
exitcode(REG(d0) LONG status, REG(d1) LONG exitmessage)
{
  ((struct DaemonMessage *)exitmessage)->dm_Retval = status;
  ReplyMsg((struct Message *)exitmessage);
  return 0;
}

void
exitcode_end(void)
{
}
#undef SysBase

#define exitcode_len (((char *)exitcode_end)-((char *)exitcode))
#else
#error Unsupported compiler
#endif

void *WindowPtr = NULL;

/* 
 * Initialize Amiga Specific Features
 */
void 
amigainit(void)
{
  struct DaemonPort *dp, *dpo;
  BYTE msgbit;
  struct Process *me = (struct Process *)FindTask(NULL);
  int old_is_alive = 0;

  atexit(amigadeinit);

  /* Remove requesters */
  WindowPtr = me->pr_WindowPtr;
  me->pr_WindowPtr = (void *)-1L;

  if ((msgbit = AllocSignal(-1)) == -1) {
    (void)fprintf(stderr, "inetd: no free signals\n");
    exit(2);
  }

  /* Allocate a global port for DaemonMessages */
  if (dp = AllocVec(sizeof(*dp) + exitcode_len + sizeof(DAEMONPORTNAME),
		    MEMF_CLEAR|MEMF_PUBLIC)) {
    dp->dp_Port.mp_Node.ln_Type = NT_MSGPORT;
    dp->dp_Port.mp_SigTask = me;
    dp->dp_Port.mp_Flags = PA_SIGNAL;
    dp->dp_Port.mp_SigBit = msgbit;
    memcpy(dp->dp_ExitCode = (char *)(dp + 1), exitcode, exitcode_len);
    memcpy(dp->dp_Port.mp_Node.ln_Name = exitcode_len + (UBYTE *)(dp + 1),
	   DAEMONPORTNAME, sizeof(DAEMONPORTNAME));
    /* Flush copied function */
    CacheClearE(dp->dp_ExitCode, exitcode_len, CACRF_ClearI);
  }

  Forbid();

  dpo = (struct DaemonPort*)FindPort(DAEMONPORTNAME);
  if (!dpo) {
    if (dp) AddPort(dp);
  } else {
    FreeVec(dp);
    dp = dpo;
    if (dpo->dp_Port.mp_SigTask == NULL) {
      dpo->dp_Port.mp_SigTask = me;
      dpo->dp_Port.mp_SigBit = msgbit;
      dpo->dp_Port.mp_Flags = PA_SIGNAL;
      SetSignal(1 << msgbit, 1 << msgbit); /* check old messages */
    } else {
      old_is_alive = 1;
    }
  }
  Permit();

  if (old_is_alive || !dp) {
    FreeSignal(msgbit);
    (void)fprintf(stderr, old_is_alive ? 
		  "inetd: already exists\n" :
		  "inetd: memory exhausted\n");
    exit(2);
  }

  dport = dp;

  if (!(tport = CreateMsgPort())) {
    (void)fprintf(stderr, "inetd: could not create message port\n");
    exit(2);
  }
  if (!(tmsg = CreateIORequest(tport, sizeof(*tmsg)))){
    (void)fprintf(stderr, "inetd: could not create timer message\n");
    exit(2);
  }

  tmsg->tr_node.io_Message.mn_Node.ln_Type = NT_UNKNOWN; 
  tmsg->tr_node.io_Command = TR_ADDREQUEST;

  if (OpenDevice(TIMERNAME, UNIT_MICROHZ, 
		 (struct IORequest *)tmsg, 0)) {
    (void)fprintf(stderr, "inetd: could not open %s\n", TIMERNAME);
    exit(2);
  }
  timer_is_open = 1;
}

/* 
 * Deinitialize Amiga Specific stuff
 */
void
amigadeinit(void)
{
  struct Process *me = (struct Process *)FindTask(NULL);

  /* Resume requesters for this process */
  me->pr_WindowPtr = WindowPtr;

  if (tmsg && timer_is_open) {
    if (tmsg->tr_node.io_Message.mn_Node.ln_Type != NT_UNKNOWN 
	&& !CheckIO(tmsg)) {
      AbortIO(tmsg);
      WaitIO(tmsg);
    }
  }
  if (timer_is_open) {
    CloseDevice(tmsg);
    timer_is_open = 0;
  }
  if (tmsg) {
    DeleteIORequest(tmsg);
  }
  if (tport) {
    DeleteMsgPort(tport);
  }

  if (dport) {
    BYTE sigbit;
    int i;

    /* Try to kill all our builtin children */
    for (i = builtins_started - 1; i >= 0; i--)
      Signal((struct Task*)ptable[i], SIGBREAKF_CTRL_C);
    /* Close all sockets */
    for (i = NOFILE - 1; i >= 0; i--) {
      CloseSocket(i);
    }
    /* Wait until all childs are terminated */
    while (builtins_started > 0) {
      WaitPort(dport);
      reapchild();
    }

    Forbid();
    sigbit = dport->dp_Port.mp_SigBit;
    dport->dp_Port.mp_Flags = PA_IGNORE;
    dport->dp_Port.mp_SigBit = (UBYTE)-1; /* braindead */
    dport->dp_Port.mp_SigTask = NULL;
    FreeSignal(sigbit);
    Permit();
  }
}

/*
 * Generic printing functions 
 */
void ASM 
stuffchar(REG(d0) char ch, REG(a3) struct CSource * sc)
{
  if (sc->CS_CurChr < sc->CS_Length 
      && (sc->CS_Buffer[sc->CS_CurChr] = ch))
    sc->CS_CurChr++;
}

static ULONG
csprintf(struct CSource *buf, const char *fmt, ...)
{
  ULONG start = buf->CS_CurChr;
  va_list ap;

  if (buf->CS_Length && buf->CS_CurChr < buf->CS_Length) {
    va_start(ap, fmt);
    RawDoFmt((STRPTR)fmt, ap, stuffchar, buf);
    va_end(ap);
    
    if (buf->CS_CurChr == buf->CS_Length) {
      buf->CS_CurChr--;			/* must NUL terminate */
    }      
    buf->CS_Buffer[buf->CS_CurChr] = '\0';

    return buf->CS_CurChr - start;
  } else {
    /* A pathological case */
    return 0;
  }
}
