/*  $Revision: 1.11 $
**
**  Send control messages to the InterNetNews daemon.
*/
#include <stdio.h>
#include <sys/types.h>
#include "configdata.h"
#include "clibrary.h"
#include <ctype.h>
#include <errno.h>
#include <sys/stat.h>
#include "libinn.h"
#include "inndcomm.h"
#include "paths.h"
#include "macros.h"
#include <syslog.h>  


/*
**  Datatype for an entry in the command table.
*/
typedef struct _COMMAND {
    STRING	Command;
    STRING	Text;
    int		argc;
    char	Letter;
    BOOL	Glue;
} COMMAND;


STATIC COMMAND	Commands[] = {
    {	"addhist",	"id arr exp post path...\tAdd history line",
	5,	SC_ADDHIST,	TRUE	},
    {	"allow",	"reason...\t\t\tAllow remote connections",
	1,	SC_ALLOW,	TRUE	},
    {	"begin",	"site\t\t\tStart newly-added site",
	1,	SC_BEGIN,	FALSE	},
    {	"cancel",	"id\t\t\tCancel message locally",
	1,	SC_CANCEL,	FALSE	},
    {	"changegroup",	"group rest\tChange mode of group",
	2,	SC_CHANGEGROUP,	FALSE	},
    {	"checkfile",	"\t\t\tCheck syntax of newsfeeds file",
	0,	SC_CHECKFILE,	FALSE	},
    {	"drop",		"site\t\t\tStop feeding site",
	1,	SC_DROP,	FALSE		},
    {	"feedinfo",		"site\t\t\tPrint state of feed to site*",
	1,	SC_FEEDINFO,	FALSE		},
#if defined(DO_TCL)
    {	"tcl",			"flag\t\t\tEnable or disable Tcl filtering",
	1,	SC_FILTER,	FALSE		},
#endif /* defined(DO_TCL) */
    {	"flush",	"site\t\t\tFlush feed for site*",
	1,	SC_FLUSH,	FALSE	},
    {	"flushlogs",	"\t\t\tFlush log files",
	0,	SC_FLUSHLOGS,	FALSE	},
    {	"go",		"reason...\t\t\tRestart after pause or throttle",
	1,	SC_GO,		TRUE	},
    {	"hangup",	"channel\t\tHangup specified incoming channel",
	1,	SC_HANGUP,	FALSE	},
    {	"logmode",		"\t\t\t\tSend server mode to syslog",
	0,	SC_LOGMODE,	FALSE		},
    {	"mode",		"\t\t\t\tPrint operating mode",
	0,	SC_MODE,	FALSE		},
    {	"name",		"nnn\t\t\tPrint name of specified channel*",
	1,	SC_NAME,	FALSE		},
    {	"newgroup",	"group rest creator\tCreate new group",
	3,	SC_NEWGROUP,	FALSE	},
    {	"param",	"letter value\t\tChange command-line parameters",
	2,	SC_PARAM,	FALSE	},
    {	"pause",	"reason...\t\tShort-term pause in accepting articles",
	1,	SC_PAUSE,	TRUE	},
#if defined(DO_PERL)
    {	"perl",			"flag\t\t\tEnable or disable Perl filtering",
	1,	SC_PERL,	FALSE	},
#endif /* defined(DO_PERL) */
    {	"readers",	"flag text...\t\tEnable or disable newsreading",
	2,	SC_READERS,	TRUE	},
    {	"reject",	"reason...\t\t\tReject remote connections",
	1,	SC_REJECT,	TRUE	},
    {	"reload",	"what reason...\t\tRe-read config files*",
	2,	SC_RELOAD,	TRUE	},
    {	"renumber",	"group\t\tRenumber the active file*",
	1,	SC_RENUMBER,	FALSE	},
    {	"reserve",	"reason...\t\tReserve the next pause or throttle",
	1,	SC_RESERVE,	TRUE	},
    {	"rmgroup",	"group\t\t\tRemove named group",
	1,	SC_RMGROUP,	FALSE	},
    {	"send",		"feed text...\t\tSend text to exploder feed",
	2,	SC_SEND,	TRUE	},
    {	"shutdown",	"reason...\t\tShut down server",
	1,	SC_SHUTDOWN,	TRUE	},
    {	"status",	"interval|off\t\tTurn innd status generation on or off",
	1,	SC_STATUS,	FALSE	},
    {	"kill",	"signal site\t\tSend signal to site's process",
	2,	SC_SIGNAL,	FALSE	},
    {	"throttle",	"reason...\t\tStop accepting articles",
	1,	SC_THROTTLE,	TRUE	},
    {   "timer",	"interval|off\t\tTurn performance monitoring on or off",
	1,	SC_TIMER,	FALSE	},
    {	"trace",	"innd|#|nnrpd flag\tTurn tracing on or off",
	2,	SC_TRACE,	FALSE	},
    {	"xabort",	"text...\t\tAbort the server",
	1,	SC_XABORT,	TRUE	},
    { "lowmark",	"filename\t\tReset active file low article marks",
	1,	SC_LOWMARK,	FALSE	},
    { "renumberlow",	"filename\t\tReset active file low article marks",
	1,	SC_LOWMARK,	FALSE	},
    {	"xexec",	"path\t\t\tExec new server",
	1,	SC_XEXEC,	FALSE	}
};



/*
**  Print a help summary.
*/
STATIC NORETURN
Help(p)
    char		*p;
{
    register COMMAND	*cp;

    if (p == NULL) {
	(void)printf("Command summary:\n");
	for (cp = Commands; cp < ENDOF(Commands); cp++)
	    (void)printf("  %s %s\n", cp->Command, cp->Text);
	(void)printf("*   Empty string means all sites/groups/etc.\n");
	(void)printf("... All trailing words are glued together.\n");
	exit(0);
    }
    for (cp = Commands; cp < ENDOF(Commands); cp++)
	if (EQ(p, cp->Command)) {
	    (void)printf("Command usage:\n");
	    (void)printf("  %s %s\n", cp->Command, cp->Text);
	    exit(0);
	}
    (void)printf("No such command.\n");
    exit(0);
}


/*
**  Print a command-usage message and exit.
*/
STATIC NORETURN
WrongArgs(cp)
    COMMAND	*cp;
{
    (void)printf("Wrong number of arguments -- usage:\n");
    (void)printf("  %s %s\n", cp->Command, cp->Text);
    exit(1);
}


/*
**  Print an error message and exit.
*/
STATIC NORETURN
Failed(p)
    char	*p;
{
    if (ICCfailure)
	(void)fprintf(stderr, "Can't %s (%s failure) %s.\n",
		p, ICCfailure, strerror(errno));
    else
	(void)fprintf(stderr, "Can't %s, %s.\n", p, strerror(errno));
    (void)ICCclose();
    exit(1);
}


/*
**  Print an error reporting incorrect usage.
*/
STATIC NORETURN
Usage(what)
    char	*what;
{
    (void)fprintf(stderr, "Usage error (%s) -- try -h for help.\n", what);
    exit(1);
}


int main(int ac, char *av[])
{
    static char		Y[] = "y";
    static char		EMPTY[] = "";
    COMMAND	        *cp;
    char	        *p;
    int	                i;
    BOOL		Silent;
    BOOL		NeedHelp;
    char		*reply;
    char		*new;
    int			length;
    char		*nv[4];
    struct stat		Sb;
    char		buff[SMBUF];

    /* First thing, set up logging and our identity. */
    openlog("ctlinnd", L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);        

    /* Set defaults. */
    if (ReadInnConf() < 0) exit(1);
    Silent = FALSE;
    NeedHelp = FALSE;
    ICCsettimeout(CTLINND_TIMEOUT);

    /* Parse JCL. */
    while ((i = getopt(ac, av, "hst:")) != EOF)
	switch (i) {
	default:
	    Usage("bad flags");
	    /* NOTREACHED */
	case 'h':		/* Get help			*/
	    NeedHelp = TRUE;
	    break;
	case 's':		/* Silent -- no output		*/
	    Silent = TRUE;
	    break;
	case 't':		/* Time to wait for reply	*/
	    ICCsettimeout(atoi(optarg));
	    break;
	}
    ac -= optind;
    av += optind;
    if (NeedHelp)
	Help(av[0]);
    if (ac == 0)
	Usage("missing command");

    /* Look up the command word and move to the arguments. */
    if (EQ(av[0], "help"))
	Help(av[1]);
    for (cp = Commands; cp < ENDOF(Commands); cp++)
	if (EQ(av[0], cp->Command))
	    break;
    if (cp == ENDOF(Commands))
	Usage("unknown command");
    ac--;
    av++;

    /* Check argument count. */
    if (cp->Letter == SC_NEWGROUP) {
	/* Newgroup command has defaults. */
	switch (ac) {
	default:
	    WrongArgs(cp);
	    /* NOTREACHED */
	case 1:
	    nv[0] = av[0];
	    nv[1] = Y;
	    nv[2] = EMPTY;
	    nv[3] = NULL;
	    av = nv;
	    break;
	case 2:
	    nv[0] = av[0];
	    nv[1] = av[1];
	    nv[2] = EMPTY;
	    nv[3] = NULL;
	    av = nv;
	    break;
	case 3:
	    break;
	}
	switch (av[1][0]) {
	default:
	    Usage("Bad group mode");
	    /* NOTREACHED */
	case NF_FLAG_ALIAS:
	case NF_FLAG_EXCLUDED:
	case NF_FLAG_MODERATED:
	case NF_FLAG_OK:
	case NF_FLAG_NOLOCAL:
	case NF_FLAG_IGNORE:
	    break;
	}
	ac = 3;
    }
    else if (ac > cp->argc && cp->Glue) {
	/* Glue any extra words together. */
	for (length = 0, i = cp->argc - 1; (p = av[i++]) != NULL; )
	    length += strlen(p) + 1;
	for (new = p = NEW(char, length), i = cp->argc - 1; av[i]; i++) {
	    if (i >= cp->argc)
		*p++ = ' ';
	    p += strlen(strcpy(p, av[i]));
	}
	av[cp->argc - 1] = new;
	av[cp->argc] = NULL;
    }
    else if (ac != cp->argc)
	/* All other commands must have the right number of arguments. */
	WrongArgs(cp);

    /* Make sure there are no separators in the parameters. */
    for (i = 0; (p = av[i++]) != NULL; )
	if (strchr(p, SC_SEP) != NULL) {
	    (void)fprintf(stderr, "Illegal character '\\%03o' in \"%s\"\n",
		    SC_SEP, p);
	    exit(1);
	}

    /* Do the real work. */
    if (ICCopen() < 0)
	Failed("setup communication");
    i = ICCcommand(cp->Letter, av, &reply);
    if (i < 0) {
	i = errno;
	p = cpcatpath(innconf->pathrun, _PATH_SERVERPID);
	if (stat(p, &Sb) < 0)
	    (void)fprintf(stderr, "No innd.pid file; did server die?\n");
	(void)sprintf(buff, "send \"%s\" command", cp->Command);
	errno = i;
	Failed(buff);
    }

    if (reply) {
	/* Skip "<exitcode><space>" part of reply. */
	for (p = reply; *p && CTYPE(isdigit, *p); p++)
	    continue;
	while (*p && ISWHITE(*p))
	    p++;
	if (i != 0)
	    (void)fprintf(stderr, "%s\n", p);
	else if (!Silent)
	    (void)printf("%s\n", p);
    }

    if (ICCclose() < 0)
	Failed("end communication");

    exit(i);
    /* NOTREACHED */
}
