    /*********************************************************************\
    *  Copyright (c) 1991 by Wen-King Su (wen-king@vlsi.cs.caltech.edu)   *
    *                                                                     *
    *  You may copy or modify this file in any manner you wish, provided  *
    *  that this notice is always included, and that you hold the author  *
    *  harmless for any loss or damage resulting from the installation or *
    *  use of this software.                                              *
    \*********************************************************************/

#include "server_def.h"
#ifdef __STDC__
#include <stdarg.h>
#else
#include <varargs.h>
#endif

#ifdef VMS
#define STAT vms_stat
#else
#define STAT stat
#endif

#define ACTIONLOG0(X) \
do{if(!old){ \
	fsplogs(); \
	fsploga("%s %s", inetstr, (X)); \
}}while (0)

#define ACTIONLOG1(X) \
do{if(!old){ \
	fsplogs(); \
	fsploga("%s %-8s /%.*s", inetstr, (X), l1, s1); \
}}while (0)

#define ACTIONINFO(F) \
do{if(!old){ \
	fsploga F; \
}}while (0)

#define ACTIONFAILED(M) \
do{if(!old){ \
	fsploga(": ERROR %s\n", (M)); \
	fsplogf(); \
	send_error(from, ub, (M)); \
}}while (0)

#define ACTIONOK() \
do{if(!old){ \
	fsploga("\n"); \
	fsplogf(); \
}}while (0)

#ifdef VMS  /* for the freopen() function */
# define NULL_DEV "nl:"
#else
# define NULL_DEV "/dev/null"
#endif

int max_nlen         = FILE_NAME_LIMIT;
int inetd_mode       =               0;
int dbug             =               0;
int logging          = 		     0;
int priv_mode        =               0;
int read_only        =               0;
int no_unnamed       =               0;
int   udp_port       =    DEF_FSP_PORT;
#ifdef VMS
char home_dir[80]    =    DEF_FSP_HOME;
char run_uid[40]     =            "\0";
char logname[80]     =     DEF_FSP_LOG;
char readme_file[80] =	     ".README";
#else
char *home_dir       =    DEF_FSP_HOME;
char *run_uid        =               0;
char *logname        =     DEF_FSP_LOG;
char *readme_file    =       ".README";
#endif

int logfd = -1;  /* logfile file descriptor */

int always_use_cache_dir = ALWAYS_USE_CACHE_DIR;
int dir_cache_limit      = MAX_DIR_CACHE_COUNT;
char *dir_cache_dir      = DEF_DIRECTORY_CACHE;

extern unsigned int maxthcallowed;

/****************************************************************************
 * A slightly better logging function.. It now takes a format string and    *
 * any number of args.                                                      *
 * The file is opened and closed so that I can run a cron job to summarize  *
 * the file, then remove the already summarized portion automatically       *
 * without worrying about the file descriptors pointing to a file that no   *
 * longer really exists.                                                    *
 ****************************************************************************/
#define LOGBUFFER 1024
static char logbuf[LOGBUFFER];	/* buffer for log message */
static int logpos = 0;		/* current log message length */

/* append some text to the log message */
#ifdef __STDC__
void fsploga(const char *fmt, ...)
#else
void fsploga(va_alist)
va_dcl
#endif
{
#ifdef __STDC__
    va_list args;
#else
    char *fmt;
    va_list args;
#endif

    if(logging && logfd >= 0)  /* don't log if we don't have a logfile */
    {
#ifdef __STDC__
	va_start(args, fmt);
#else
	va_start(args);
	fmt = va_arg(args, char *);
#endif
	vsprintf(logbuf + logpos, fmt, args);
	logpos += strlen(logbuf + logpos);
	va_end(args);
    }
}

/* add a datestamp to the log message */
void fsplogs()
{
    struct stat sb;

    if(logging && logfd >= 0 && STAT(logname, &sb) == -1) {
	close(logfd);
#ifdef VMS
	if((logfd = vms_open(logname, O_WRONLY)) < 0)
#else
	if((logfd = open(logname, O_WRONLY | O_APPEND | O_CREAT, 0644)) < 0)
#endif
	    logging = 0;
    }
    if(logging && logfd >= 0)  /* don't log if we don't have a logfile */
    {
	time_t sectime; /* current time in seconds */
	int timelen;
	char *timestr;

	sectime = time((time_t *)0);
	timestr = ctime(&sectime);
	/* never count on time strings being the same length */
	/* *(timestr+24)=0; /* strip the last CR from time */
	timelen = strlen(timestr) - 1; /* strip the CR */
	timestr[timelen] = '\0';
	strcpy(logbuf + logpos, timestr);
	logbuf[timelen] = ' ';
	logpos += timelen + 1;
    }
}

/* flush the log message to file */
void fsplogf()
{
    if(logging && logfd >= 0)  /* don't log if we don't have a logfile */
    {
	write(logfd, logbuf, logpos);
	logpos = 0;
    }
}

/* datestamp the message and flush it to file */
#ifdef __STDC__
void fsplog(const char *fmt, ...)
#else
void fsplog(va_alist)
va_dcl
#endif
{
#ifdef __STDC__
    va_list args;
#else
    char *fmt;
    va_list args;
#endif

    if(logging && logfd >= 0)  /* don't log if we don't have a logfile */
    {
#ifdef __STDC__
	va_start(args, fmt);
#else
	va_start(args);
	fmt = va_arg(args, char *);
#endif
	fsplogs();		/* add the date stamp to the log buffer */
	vsprintf(logbuf + logpos, fmt, args);
	logpos += strlen(logbuf + logpos);
	fsplogf();		/* flush the log message to the file */
	va_end(args);
    }
}

/****************************************************************************
* Send version information.
* Note: no bounds checking is currently performed.  As version information
*       grows, this will become required.
****************************************************************************/
server_show_version(from, ub)
    struct sockaddr_in *from;
    UBUF *ub;
{
    char buf[UBUF_SPACE], verflags = 0;

    strcpy(buf, VERSION_STR);
    strcat(buf, "\n");

    if(logging) verflags |= VER_LOG;

    if (read_only) verflags |= VER_READONLY;

    if (no_unnamed) verflags |= VER_REVNAME;

    if (priv_mode) verflags |= VER_PRIVMODE;

    if (maxthcallowed) verflags |= VER_THRUPUT;

    strcpy(ub->buf, buf);
    ub->pos = VER_BYTES;
    ub->buf[strlen(ub->buf)] = '\0';
    ub->buf[strlen(ub->buf)+1] = verflags;
    if(maxthcallowed) {
	ub->pos += 4;
	ub->buf[strlen(ub->buf)+2] = (char)((maxthcallowed & 0xff000000)>>24);
	ub->buf[strlen(ub->buf)+3] = (char)((maxthcallowed & 0x00ff0000)>>16);
	ub->buf[strlen(ub->buf)+4] = (char)((maxthcallowed & 0x0000ff00)>>8);
	ub->buf[strlen(ub->buf)+5] = (char)(maxthcallowed & 0x000000ff);
	
	server_reply(from, ub, strlen(ub->buf)+1, VER_BYTES+4);
    } else {
	server_reply(from, ub, strlen(ub->buf)+1, VER_BYTES);
    }
}

/****************************************************************************
*  This is the dispatch loop for message that has been accepted.
*    bytes: size of the message received.
*       ub: pointer to the message buffer.
*      old: true if this message contains old sequence number (retransmit).
*       hp: pointer to the entry for the client host who sent this message.
*     from: pointer to the socket address structure of the client host.
****************************************************************************/

server_get_packet(bytes,ub,old,hp,from)
    int   bytes,old;
    UBUF *ub;
    HTAB *hp;
    struct sockaddr_in *from;
{
    unsigned long  inet_num;
    unsigned short port_num;
    unsigned l1, l2;
    char *s1, *s2, *pe, inetstr_buf[128], *inetstr;
    FILE *fp;
    PPATH pp;
    struct stat sd; /* for logging of filesize */

    l1 = ub->len;
    l2 = bytes - l1 - UBUF_HSIZE;
    s1 = ub->buf;
    s2 = ub->buf + l1;

    /* put remote inet_number in a var, for logging purposes */
    if (dbug || logging) {
        if (hp->hostname)
	    inetstr = hp->hostname;
	else {
	    sprintf(inetstr_buf,"%03d.%03d.%03d.%03d",
		    ((unsigned char *)(&hp->inet_num))[0],
		    ((unsigned char *)(&hp->inet_num))[1],
		    ((unsigned char *)(&hp->inet_num))[2],
		    ((unsigned char *)(&hp->inet_num))[3]);
	    inetstr = inetstr_buf;
	}
    }

    if(dbug) fprintf(stderr,"rcv (%c,%d,%d,%lu) <--- %s\n",
		     ub->cmd, l1, l2, ub->pos, inetstr);

    if(!old) { hp->last_key = hp->next_key;
	       hp->next_key = get_next_key() + ((hp->last_key+1) & 0x00ff); }

    ub->key  = hp->next_key;
    inet_num = hp->inet_num;
    port_num = from->sin_port;

    switch(ub->cmd)
    {
      case CC_VERSION : { ACTIONLOG0("VERSION");
			  server_show_version(from, ub);
			  ACTIONOK();
			  return; }

      case CC_BYE     : { if(!old) hp->active = 0;
			  server_reply(from,ub,0,0);
			  return; }
			
      case CC_GET_DIR : { if (!ub->pos) ACTIONLOG1("GETDIR");
			  if((pe = check_path(s1,l1,&pp)) ||
			     (pe = server_get_dir(&pp, inet_num,&fp))) {
			      if (ub->pos) ACTIONLOG1("GETDIR");
			      ACTIONFAILED(pe);
			      return;
			  }
			  send_file(from,ub,fp,l2,s2);
			  fclose(fp);
			  if (!ub->pos) ACTIONOK();
			  return; }

      case CC_GET_FILE: { if (!ub->pos) ACTIONLOG1("GETFILE");
			  if((pe = check_path(s1,l1,&pp)) ||
		            (pe = server_get_file(&pp,&fp,inet_num,port_num))){
			     if (ub->pos) ACTIONLOG1("GETFILE");
			     ACTIONFAILED(pe);
			     return;
			  }
                          if (!ub->pos)
                          { STAT(pp.fullp,&sd); /* log filesizes */
                            ACTIONINFO((" (%d)",sd.st_size));
                          }
			  send_file(from,ub,fp,l2,s2);
			  if (!ub->pos) ACTIONOK();
			  return; }

      case CC_DEL_FILE: { ACTIONLOG1("DELFILE");
			  if (read_only)
				{ ACTIONFAILED("Permission denied"); return; }
			  if(!old)
			    if((pe = check_path(s1,l1,&pp)) ||
			       (pe = server_del_file(&pp,inet_num))) {
				ACTIONFAILED(pe);
				return;
			  }
			  server_reply(from,ub,0,0);
			  ACTIONOK();
			  return; }

      case CC_DEL_DIR : { ACTIONLOG1("DELDIR");
		          if (read_only)
				{ ACTIONFAILED("Permission denied"); return; }
			  if(!old)
			    if((pe = check_path(s1,l1,&pp)) ||
			       (pe = server_del_dir(&pp,inet_num)))
				{ ACTIONFAILED(pe); return; }
			  server_reply(from,ub,0,0);
			  ACTIONOK();
			  return; }

      case CC_UP_LOAD : { if (!ub->pos || read_only) {
				ACTIONLOG0("UPLOAD");
			  	if (read_only)
				{
				    ACTIONFAILED("Permission denied"); return;
				}
			  }
			  if(!old)
			    if(pe = server_up_load(s1,l1,ub->pos,
						   inet_num,port_num)) {
				if (ub->pos) ACTIONLOG0("UPLOAD");
				ACTIONFAILED(pe);
				return;
			    }
			  server_reply(from,ub,0,0);
			  if(!ub->pos) ACTIONOK();
			  return; }

      case CC_INSTALL : { ACTIONLOG1("INSTALL");
			  if (read_only)
				{ ACTIONFAILED("Permission denied"); return; }
			  if(!old)
			    if((pe = check_path(s1,l1,&pp)) ||
			       (pe = server_install(&pp,inet_num,port_num)))
				{ ACTIONFAILED(pe); return; }
			  server_reply(from,ub,0,0);
			  ACTIONOK();
			  return; }

      case CC_MAKE_DIR: { ACTIONLOG1("MAKEDIR");
			  if (read_only)
				{ ACTIONFAILED("Permission denied"); return; }
			  if(!old)
			    if((pe = check_path(s1,l1,&pp)) ||
			       (pe=server_make_dir(&pp,inet_num)))
				 { ACTIONFAILED(pe); return; }
			  if(pe = server_get_pro(&pp,s1,inet_num))
				 { ACTIONFAILED(pe); return; }
			  server_reply(from,ub,strlen(ub->buf)+1,0);
			  ACTIONOK();
			  return; }

      case CC_GET_PRO : { ACTIONLOG1("GETPRO");
			  if((pe = check_path(s1,l1,&pp)) ||
			     (pe=server_get_pro(&pp,s1,inet_num)))
				{ ACTIONFAILED(pe); return; }
			  ub->pos = PRO_BYTES;
			  server_reply(from,ub,strlen(ub->buf)+1,PRO_BYTES);
			  ACTIONOK();
			  return; }

      case CC_SET_PRO : { ACTIONLOG1("SETPRO");
			  if(read_only)
				{ ACTIONFAILED("Permission denied"); return; }
			  if(!old)
			    if((pe = check_path(s1,l1,&pp)) ||
			       (pe = server_set_pro(&pp,s2,inet_num)))
				{ ACTIONFAILED(pe); return; }
			  if(pe = server_get_pro(&pp,s1,inet_num))
			    { ACTIONFAILED(pe); return; }
			  ub->pos = PRO_BYTES;
			  server_reply(from,ub,strlen(ub->buf)+1,PRO_BYTES);
			  ACTIONOK();
			  return; }

      case CC_GRAB_FILE: { if (!ub->pos || read_only) {
			       ACTIONLOG1("GRABFILE");
			       if (read_only)
				  {ACTIONFAILED("Permission denied"); return;}
			   }
			   if(pe = check_path(s1,l1,&pp)) {
				if (ub->pos) ACTIONLOG1("GRABFILE");
				{ ACTIONFAILED(pe); return; }
			   }
			   if(!old && !ub->pos)
			   {
			      if(pe=server_secure_file(&pp,inet_num,port_num))
			        { ACTIONFAILED(pe); return; }
			   }
			   if(pe = server_grab_file(&pp,&fp,inet_num,port_num)){
				if (ub->pos) ACTIONLOG1("GRABFILE");
				{ ACTIONFAILED(pe); return; }
			   }
			   send_file(from,ub,fp,l2,s2);
			   fclose(fp);
			   if (!ub->pos) ACTIONOK();
			   return; }

      case CC_GRAB_DONE: { ACTIONLOG1("GRABDONE");
			   if (read_only)
				{ ACTIONFAILED("Permission denied"); return;}
			   if(pe = check_path(s1,l1,&pp))
				{ ACTIONFAILED(pe); return;}
			   if(!old)
			     if(pe = server_grab_done(&pp,inet_num,port_num))
				{ ACTIONFAILED(pe); return; }
			   server_reply(from,ub,0,0);
			   ACTIONOK();
			   return; }

      default:		{ ACTIONLOG0("UNKNOWN");
			  ACTIONINFO((" (%d)", ub->cmd));
			  ACTIONFAILED("Unknown FSP command");
			  return; }
    }
}

arg_err()
{
    fputs("arg: -h home_dir    set home directory.\n",stderr);
    fputs("     -p udp_port    set port number.\n",stderr);
    fputs("     -u uid_number  assume this uid after startup.\n",stderr);
    fputs("     -d             turn on debug mode.\n",stderr);
    fputs("     -r             run server in priveledged mode.\n", stderr);
    fputs("     -w             run in read-only mode.\n",stderr);
    fputs("     -n             refuse machine which can't be named.\n",stderr);
    fputs("     -l [logfile]   turn on logging.\n", stderr);
    fputs("     -t throughput  turn on thruput control (in bytes)\n", stderr);
    fputs("     -m filename    set the per directory README file.\n", stderr);
    exit(1);
}

main(argc,argv)
    int argc;
    char **argv;
{
    int t;

    inetd_mode = !strcmp(argv[0],"in.fspd");
    maxthcallowed = 0;

    if(inetd_mode)
    {
	init_inetd();
	freopen(NULL_DEV,"r",stdin);
	freopen(NULL_DEV,"w",stdout);
	freopen(NULL_DEV,"w",stderr);
    }

    for(t = 1; t < argc; t++)
    {
	if(argv[t][0] == '-') switch(argv[t][1])
	{
	    case 'd':	dbug++; break;
	    case 'r':   priv_mode++; break;
	    case 'w':	read_only++; break;
	    case 'n':	no_unnamed++; break;
	    case 'm':
#ifdef VMS
			if(argv[t][2]) strcpy(readme_file, argv[t]+2); else
			if(argv[t+1] ) strcpy(readme_file, argv[++t]); else
			    arg_err();
#else
	    		if(argv[t][2]) readme_file = argv[t]+2; else
			if(argv[t+1] ) readme_file = argv[++t]; else arg_err();
#endif
			break;
	    case 'h':
#ifdef VMS
			if(argv[t][2]) strcpy(home_dir, argv[t]+2); else
			if(argv[t+1] ) strcpy(home_dir, argv[++t]); else
			    arg_err();
#else
	    		if(argv[t][2]) home_dir = argv[t]+2; else
			if(argv[t+1] ) home_dir = argv[++t]; else arg_err();
#endif
			break;
	    case 'u':
#ifdef VMS
	    		if(argv[t][2]) strcpy(run_uid, argv[t]+2); else
			if(argv[t+1] ) strcpy(run_uid, argv[++t]); else
			    arg_err();
#else
	    		if(argv[t][2]) run_uid = argv[t]+2; else
			if(argv[t+1] ) run_uid = argv[++t]; else arg_err();
#endif
			break;
	    case 'l':
#ifdef VMS
			logging = 1;
			if(argv[t][2]) strcpy(logname, argv[t]+2); else
			if(argv[t+1] && argv[t+1][0] != '-')
			    strcpy(logname, argv[++t]);
			/* else log to compiled in default file */
#else
			logging = 1;
			if(argv[t][2]) logname = argv[t]+2; else
			if(argv[t+1] && argv[t+1][0]!= '-')
			    logname = argv[++t];
			/* else log to compiled in default file */
#endif
			break;
	    case 'p':	if(argv[t][2]) udp_port = atoi(argv[t]+2); else
			if(argv[t+1] ) udp_port = atoi(argv[++t]); else
								arg_err();
			break;
	    case 't':	if(argv[t][2]) maxthcallowed = atoi(argv[t]+2); else
			if(argv[t+1] ) maxthcallowed = atoi(argv[++t]); else
			arg_err();
			break;
	    default : arg_err();

	} else arg_err();
    }

#ifdef VMS
    if(argc==1) /* no arguments given, read them from stdin.
		 * kludge to enter the args when using the RUN/detach
		 * command on VMS */
    {
	printf("homedir: ");scanf("%s",home_dir);
	printf("udpport: ");scanf("%d",&udp_port);
	printf("run_uid: ");scanf("%s",run_uid);
	printf("debug  : ");scanf("%d",&dbug);
	printf("logfile: ");scanf("%s",logname); logging=1;
    }
#endif

    /* clear all entries in file-cache */
    clear_cache(fpcache, &cache_p);

/* Moved setuid to here from below because log file was getting opened
   by root, and fspd could no longer write to the log file after the
   setuid. This should always open the file as run_uid
   Modified A.E.J.Fellows 9 March 93	*/

    if(run_uid) if(setuid(atoi(run_uid)) != 0) exit(1);

    if (logging && *logname)
    {
	/* test to see if logfile can be written */
	/* open it append mode so that it doesn't wipe the file when
	 * you are running under inetd. VMS uses file version number, so
         * the old logfile won't be erased.
	 */
#ifdef VMS
        if((logfd=vms_open(logname, O_WRONLY)) < 0)
#else
	if((logfd=open(logname, O_WRONLY | O_APPEND | O_CREAT, 0644)) < 0)
#endif
	{
	    fprintf(stderr, "Error opening logfile: %s, logging disabled.\n",
		    logname);
	    logging = 0; /* no logging */
	}
    }

    if(!inetd_mode)
    {
	/* Fork and die to drop daemon into background   */
	/* Added Alban E J Fellows 12 Jan 93             */
        /* Moved by JT Traub to only do this if not running under inetd. */
#if DAEMONIZE
#ifdef VMS
	freopen(NULL_DEV,"r",stdin); /* speed up */
	freopen(NULL_DEV,"w",stdout);
	freopen(NULL_DEV,"w",stderr);
        if (vfork() > 0)
#else
	if (fork() > 0)
#endif
		exit(0);
#endif /* DAEMONIZE*/
	init_network(udp_port);
    }

    /* setuid move from here to above the logging file open */
    init_home_dir();
    init_htab();
    init_iptab();

    srandom(getpid());

    if(inetd_mode) {	      server_loop(120*1000L); }  /* 2 minutes */
	      else { while(1) server_loop(      -1L); }

    exit(0);
}
