chiark / gitweb /
Many options, and a manpage
[innduct.git] / frontends / ctlinnd.c
1 /*  $Id: ctlinnd.c 6155 2003-01-19 19:58:25Z rra $
2 **
3 **  Send control messages to the InterNetNews daemon.
4 */
5
6 #include "config.h"
7 #include "clibrary.h"
8 #include <ctype.h>
9 #include <errno.h>
10 #include <sys/stat.h>
11
12 #include "inn/innconf.h"
13 #include "inn/messages.h"
14 #include "inndcomm.h"
15 #include "libinn.h"
16 #include "paths.h"
17
18
19 /*
20 **  Datatype for an entry in the command table.
21 */
22 typedef struct _COMMAND {
23     const char *Command;
24     const char *Text;
25     int         argc;
26     char        Letter;
27     bool        Glue;
28 } COMMAND;
29
30
31 static COMMAND  Commands[] = {
32     {   "addhist",      "id arr exp post token...\tAdd history line",
33         5,      SC_ADDHIST,     true    },
34     {   "allow",        "reason...\t\t\tAllow remote connections",
35         1,      SC_ALLOW,       true    },
36     {   "begin",        "site\t\t\tStart newly-added site",
37         1,      SC_BEGIN,       false   },
38     {   "cancel",       "id\t\t\tCancel message locally",
39         1,      SC_CANCEL,      false   },
40     {   "changegroup",  "group rest\tChange mode of group",
41         2,      SC_CHANGEGROUP, false   },
42     {   "checkfile",    "\t\t\tCheck syntax of newsfeeds file",
43         0,      SC_CHECKFILE,   false   },
44     {   "drop",         "site\t\t\tStop feeding site",
45         1,      SC_DROP,        false           },
46     {   "feedinfo",             "site\t\t\tPrint state of feed to site*",
47         1,      SC_FEEDINFO,    false           },
48 #if defined(DO_TCL)
49     {   "tcl",                  "flag\t\t\tEnable or disable Tcl filtering",
50         1,      SC_FILTER,      false           },
51 #endif /* defined(DO_TCL) */
52     {   "flush",        "site\t\t\tFlush feed for site*",
53         1,      SC_FLUSH,       false   },
54     {   "flushlogs",    "\t\t\tFlush log files",
55         0,      SC_FLUSHLOGS,   false   },
56     {   "go",           "reason...\t\t\tRestart after pause or throttle",
57         1,      SC_GO,          true    },
58     {   "hangup",       "channel\t\tHangup specified incoming channel",
59         1,      SC_HANGUP,      false   },
60     {   "logmode",              "\t\t\t\tSend server mode to syslog",
61         0,      SC_LOGMODE,     false           },
62     {   "mode",         "\t\t\t\tPrint operating mode",
63         0,      SC_MODE,        false           },
64     {   "name",         "nnn\t\t\tPrint name of specified channel*",
65         1,      SC_NAME,        false           },
66     {   "newgroup",     "group rest creator\tCreate new group",
67         3,      SC_NEWGROUP,    false   },
68     {   "param",        "letter value\t\tChange command-line parameters",
69         2,      SC_PARAM,       false   },
70     {   "pause",        "reason...\t\tShort-term pause in accepting articles",
71         1,      SC_PAUSE,       true    },
72 #if defined(DO_PERL)
73     {   "perl",                 "flag\t\t\tEnable or disable Perl filtering",
74         1,      SC_PERL,        false   },
75 #endif /* defined(DO_PERL) */
76 #if defined(DO_PYTHON)
77     {   "python",               "flag\t\t\tEnable or disable Python filtering",
78         1,      SC_PYTHON,      false   },
79 #endif /* (DO_PYTHON) */
80     {   "readers",      "flag text...\t\tEnable or disable newsreading",
81         2,      SC_READERS,     true    },
82     {   "reject",       "reason...\t\t\tReject remote connections",
83         1,      SC_REJECT,      true    },
84     {   "reload",       "what reason...\t\tRe-read config files*",
85         2,      SC_RELOAD,      true    },
86     {   "renumber",     "group\t\tRenumber the active file*",
87         1,      SC_RENUMBER,    false   },
88     {   "reserve",      "reason...\t\tReserve the next pause or throttle",
89         1,      SC_RESERVE,     true    },
90     {   "rmgroup",      "group\t\t\tRemove named group",
91         1,      SC_RMGROUP,     false   },
92     {   "send",         "feed text...\t\tSend text to exploder feed",
93         2,      SC_SEND,        true    },
94     {   "shutdown",     "reason...\t\tShut down server",
95         1,      SC_SHUTDOWN,    true    },
96     {   "stathist",     "filename|off\t\tLog into filename some history stats",
97         1,      SC_STATHIST,    false   },
98     {   "status",       "interval|off\t\tTurn innd status generation on or off",
99         1,      SC_STATUS,      false   },
100     {   "kill", "signal site\t\tSend signal to site's process",
101         2,      SC_SIGNAL,      false   },
102     {   "throttle",     "reason...\t\tStop accepting articles",
103         1,      SC_THROTTLE,    true    },
104     {   "timer",        "interval|off\t\tTurn performance monitoring on or off",
105         1,      SC_TIMER,       false   },
106     {   "trace",        "innd|#|nnrpd flag\tTurn tracing on or off",
107         2,      SC_TRACE,       false   },
108     {   "xabort",       "text...\t\tAbort the server",
109         1,      SC_XABORT,      true    },
110     { "lowmark",        "filename\t\tReset active file low article marks",
111         1,      SC_LOWMARK,     false   },
112     { "renumberlow",    "filename\t\tReset active file low article marks",
113         1,      SC_LOWMARK,     false   },
114     {   "xexec",        "path\t\t\tExec new server",
115         1,      SC_XEXEC,       false   }
116 };
117
118 \f
119
120 /*
121 **  Print a help summary.
122 */
123 static void
124 Help(char *p)
125 {
126     COMMAND     *cp;
127
128     if (p == NULL) {
129         printf("Command summary:\n");
130         for (cp = Commands; cp < ARRAY_END(Commands); cp++)
131             printf("  %s %s\n", cp->Command, cp->Text);
132         printf("*   Empty string means all sites/groups/etc.\n");
133         printf("... All trailing words are glued together.\n");
134         exit(0);
135     }
136     for (cp = Commands; cp < ARRAY_END(Commands); cp++)
137         if (strcmp(p, cp->Command) == 0) {
138             printf("Command usage:\n");
139             printf("  %s %s\n", cp->Command, cp->Text);
140             exit(0);
141         }
142     printf("No such command.\n");
143     exit(0);
144 }
145
146
147 /*
148 **  Print a command-usage message and exit.
149 */
150 static void
151 WrongArgs(COMMAND *cp)
152 {
153     printf("Wrong number of arguments -- usage:\n");
154     printf("  %s %s\n", cp->Command, cp->Text);
155     exit(1);
156 }
157
158
159 /*
160 **  Print an error message and exit.
161 */
162 static void
163 Failed(const char *p)
164 {
165     if (ICCfailure)
166         syswarn("cannot %s (%s failure)", p, ICCfailure);
167     else
168         syswarn("cannot %s", p);
169     ICCclose();
170     exit(1);
171 }
172
173
174 /*
175 **  Print an error reporting incorrect usage.
176 */
177 static void
178 Usage(const char *what)
179 {
180     fprintf(stderr, "Usage error (%s) -- try -h for help.\n", what);
181     exit(1);
182 }
183
184
185 int main(int ac, char *av[])
186 {
187     static char         Y[] = "y";
188     static char         EMPTY[] = "";
189     COMMAND             *cp;
190     char                *p;
191     int                 i;
192     bool                Silent;
193     bool                NeedHelp;
194     char                *reply;
195     char                *new;
196     int                 length;
197     char                *nv[4];
198     struct stat         Sb;
199     char                buff[SMBUF];
200
201     /* First thing, set up our identity. */
202     message_program_name = "ctlinnd";
203
204     /* Set defaults. */
205     if (!innconf_read(NULL))
206         exit(1);
207     Silent = false;
208     NeedHelp = false;
209     ICCsettimeout(CTLINND_TIMEOUT);
210
211     /* Parse JCL. */
212     while ((i = getopt(ac, av, "hst:")) != EOF)
213         switch (i) {
214         default:
215             Usage("bad flags");
216             /* NOTREACHED */
217         case 'h':               /* Get help                     */
218             NeedHelp = true;
219             break;
220         case 's':               /* Silent -- no output          */
221             Silent = true;
222             break;
223         case 't':               /* Time to wait for reply       */
224             ICCsettimeout(atoi(optarg));
225             break;
226         }
227     ac -= optind;
228     av += optind;
229     if (NeedHelp)
230         Help(av[0]);
231     if (ac == 0)
232         Usage("missing command");
233
234     /* Look up the command word and move to the arguments. */
235     if (strcmp(av[0], "help") == 0)
236         Help(av[1]);
237     for (cp = Commands; cp < ARRAY_END(Commands); cp++)
238         if (strcmp(av[0], cp->Command) == 0)
239             break;
240     if (cp == ARRAY_END(Commands))
241         Usage("unknown command");
242     ac--;
243     av++;
244
245     /* Check argument count. */
246     if (cp->Letter == SC_NEWGROUP) {
247         /* Newgroup command has defaults. */
248         switch (ac) {
249         default:
250             WrongArgs(cp);
251             /* NOTREACHED */
252         case 1:
253             nv[0] = av[0];
254             nv[1] = Y;
255             nv[2] = EMPTY;
256             nv[3] = NULL;
257             av = nv;
258             break;
259         case 2:
260             nv[0] = av[0];
261             nv[1] = av[1];
262             nv[2] = EMPTY;
263             nv[3] = NULL;
264             av = nv;
265             break;
266         case 3:
267             break;
268         }
269         ac = 3;
270     }
271     else if (ac > cp->argc && cp->Glue) {
272         /* Glue any extra words together. */
273         for (length = 0, i = cp->argc - 1; (p = av[i++]) != NULL; )
274             length += strlen(p) + 1;
275         new = xmalloc(length);
276         *new = '\0';
277         for (i = cp->argc - 1; av[i]; i++) {
278             if (i >= cp->argc)
279                 strlcat(new, " ", length);
280             strlcat(new, av[i], length);
281         }
282         av[cp->argc - 1] = new;
283         av[cp->argc] = NULL;
284     }
285     else if (ac != cp->argc)
286         /* All other commands must have the right number of arguments. */
287         WrongArgs(cp);
288
289     /* For newgroup and changegroup, make sure the mode is valid. */
290     if (cp->Letter == SC_NEWGROUP || cp->Letter == SC_CHANGEGROUP) {
291         switch (av[1][0]) {
292         default:
293             Usage("Bad group mode");
294             /* NOTREACHED */
295         case NF_FLAG_ALIAS:
296         case NF_FLAG_EXCLUDED:
297         case NF_FLAG_MODERATED:
298         case NF_FLAG_OK:
299         case NF_FLAG_NOLOCAL:
300         case NF_FLAG_IGNORE:
301             break;
302         }
303     }
304
305     /* Make sure there are no separators in the parameters. */
306     for (i = 0; (p = av[i++]) != NULL; )
307         if (strchr(p, SC_SEP) != NULL)
308             die("illegal character \\%03o in %s", SC_SEP, p);
309
310     /* Do the real work. */
311     if (ICCopen() < 0)
312         Failed("setup communication");
313     i = ICCcommand(cp->Letter, (const char **) av, &reply);
314     if (i < 0) {
315         i = errno;
316         p = concatpath(innconf->pathrun, _PATH_SERVERPID);
317         if (stat(p, &Sb) < 0)
318             warn("no innd.pid file; did server die?");
319         free(p);
320         snprintf(buff, sizeof(buff), "send \"%s\" command", cp->Command);
321         errno = i;
322         Failed(buff);
323     }
324
325     if (reply) {
326         /* Skip "<exitcode><space>" part of reply. */
327         for (p = reply; *p && CTYPE(isdigit, *p); p++)
328             continue;
329         while (*p && ISWHITE(*p))
330             p++;
331         if (i != 0)
332             warn("%s", p);
333         else if (!Silent)
334             printf("%s\n", p);
335     }
336
337     if (ICCclose() < 0)
338         Failed("end communication");
339
340     exit(i);
341     /* NOTREACHED */
342 }