chiark / gitweb /
debugging for thing that crashed
[innduct.git] / nnrpd / commands.c
1 /*  $Id: commands.c 7542 2006-08-26 05:57:11Z eagle $
2 **
3 **  Miscellaneous commands.
4 */
5 #include "config.h"
6 #include "clibrary.h"
7 #include "portable/wait.h"
8
9 #include "nnrpd.h"
10 #include "ov.h"
11 #include "inn/innconf.h"
12 #include "inn/messages.h"
13
14 typedef struct {
15     char                *name;
16     int                 high;
17     int                 low;
18     int                 count;
19 } GROUPDATA;
20
21
22 extern const char *NNRPinstance;
23
24 /* returns:
25         -1 for problem (such as no such authenticator etc.)
26         0 for authentication succeeded
27         1 for authentication failed
28  */
29
30 static char *PERMauthstring;
31
32 static int
33 PERMgeneric(char *av[], char *accesslist)
34 {
35     char path[BIG_BUFFER], *fields[6], *p;
36     int i, pan[2], status;
37     pid_t pid;
38     struct stat stb;
39
40     av += 2;
41
42     PERMcanread = false;
43     PERMcanpost = false;
44     PERMaccessconf->locpost = false;
45     PERMaccessconf->allowapproved = false;
46
47     if (!*av) {
48         Reply("%d no authenticator\r\n", NNTP_SYNTAX_VAL);
49         return(-1);
50     }
51
52     /* check for ../.  I'd use strstr, but there doesn't appear to
53        be any other references for it, and I don't want to break
54        portability */
55     for (p = av[0]; *p; p++)
56         if (strncmp(p, "../", 3) == 0) {
57             Reply("%d ../ in authenticator %s\r\n", NNTP_SYNTAX_VAL, av[0]);
58             return(-1);
59         }
60
61     if (strchr(_PATH_AUTHDIR,'/') == NULL)
62         snprintf(path, sizeof(path), "%s/%s/%s/%s", innconf->pathbin,
63                  _PATH_AUTHDIR, _PATH_AUTHDIR_GENERIC, av[0]);
64     else
65         snprintf(path, sizeof(path), "%s/%s/%s", _PATH_AUTHDIR,
66                  _PATH_AUTHDIR_GENERIC, av[0]);
67
68 #if !defined(S_IXUSR) && defined(_S_IXUSR)
69 #define S_IXUSR _S_IXUSR
70 #endif /* !defined(S_IXUSR) && defined(_S_IXUSR) */
71     
72 #if !defined(S_IXUSR) && defined(S_IEXEC)
73 #define S_IXUSR S_IEXEC
74 #endif  /* !defined(S_IXUSR) && defined(S_IEXEC) */
75
76     if (stat(path, &stb) || !(stb.st_mode&S_IXUSR)) {
77         Reply("%d No such authenticator %s\r\n", NNTP_TEMPERR_VAL, av[0]);
78         return -1;
79     }
80         
81
82     /* Create a pipe. */
83     if (pipe(pan) < 0) {
84         syslog(L_FATAL, "cant pipe for %s %m", av[0]);
85         return -1;
86     }
87
88     for (i = 0; (pid = fork()) < 0; i++) {
89         if (i == innconf->maxforks) {
90             Reply("%d Can't fork %s\r\n", NNTP_TEMPERR_VAL,
91                 strerror(errno));
92             syslog(L_FATAL, "cant fork %s %m", av[0]);
93             return -1;
94         }
95         syslog(L_NOTICE, "cant fork %s -- waiting", av[0]);
96         sleep(5);
97     }
98
99     /* Run the child, with redirection. */
100     if (pid == 0) {
101         close(STDERR_FILENO);   /* Close existing stderr */
102         close(pan[PIPE_READ]);
103
104         /* stderr goes down the pipe. */
105         if (pan[PIPE_WRITE] != STDERR_FILENO) {
106             if ((i = dup2(pan[PIPE_WRITE], STDERR_FILENO)) != STDERR_FILENO) {
107                 syslog(L_FATAL, "cant dup2 %d to %d got %d %m",
108                     pan[PIPE_WRITE], STDERR_FILENO, i);
109                 _exit(1);
110             }
111             close(pan[PIPE_WRITE]);
112         }
113
114         close_on_exec(STDIN_FILENO, false);
115         close_on_exec(STDOUT_FILENO, false);
116         close_on_exec(STDERR_FILENO, false);
117
118         execv(path, av);
119         Reply("%s\r\n", NNTP_BAD_COMMAND);
120
121         syslog(L_FATAL, "cant execv %s %m", path);
122         _exit(1);
123     }
124
125     close(pan[PIPE_WRITE]);
126     i = read(pan[PIPE_READ], path, sizeof(path));
127
128     waitpid(pid, &status, 0);
129     if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
130         return 1;
131
132     if ((p = strchr(path, '\n')) != NULL)
133         *p = '\0';
134
135     if (PERMauthstring)
136         free(PERMauthstring);
137
138     PERMauthstring = xstrdup(path);
139
140     /*syslog(L_NOTICE, "%s (%ld) returned: %d %s %d\n", av[0], (long) pid, i, path, status);*/
141     /* Split "host:permissions:user:pass:groups" into fields. */
142     for (fields[0] = path, i = 0, p = path; *p; p++)
143         if (*p == ':') {
144             *p = '\0';
145             fields[++i] = p + 1;
146         }
147
148     PERMcanread = strchr(fields[1], 'R') != NULL;
149     PERMcanpost = strchr(fields[1], 'P') != NULL;
150     PERMaccessconf->allowapproved = strchr(fields[1], 'A') != NULL;
151     PERMaccessconf->locpost = strchr(fields[1], 'L') != NULL;
152     PERMaccessconf->allowihave = strchr(fields[1], 'I') != NULL;
153     if (strchr(fields[1], 'N') != NULL) PERMaccessconf->allownewnews = true;
154     snprintf(PERMuser, sizeof(PERMuser), "%s@%s", fields[2], fields[0]);
155     strlcpy(PERMpass, fields[3], sizeof(PERMpass));
156     strcpy(accesslist, fields[4]);
157     /*strcpy(writeaccess, fields[5]); future work? */
158
159     /*for (i = 0; fields[i] && i < 6; i++)
160         printf("fields[%d] = %s\n", i, fields[i]);*/
161
162     return 0;
163 }
164
165 /* ARGSUSED */
166 void
167 CMDauthinfo(ac, av)
168     int         ac;
169     char        *av[];
170 {
171     static char User[SMBUF];
172     static char Password[SMBUF];
173     char        accesslist[BIG_BUFFER];
174     char        errorstr[BIG_BUFFER];
175
176     if (strcasecmp(av[1], "generic") == 0) {
177         char *logrec = Glom(av);
178
179         strlcpy(PERMuser, "<none>", sizeof(PERMuser));
180
181         switch (PERMgeneric(av, accesslist)) {
182             case 1:
183                 PERMspecified = NGgetlist(&PERMreadlist, accesslist);
184                 PERMpostlist = PERMreadlist;
185                 syslog(L_NOTICE, "%s auth %s (%s -> %s)", ClientHost, PERMuser,
186                         logrec, PERMauthstring? PERMauthstring: "" );
187                 Reply("%d Authentication succeeded\r\n", NNTP_AUTH_OK_VAL);
188                 PERMneedauth = false;
189                 PERMauthorized = true;
190                 free(logrec);
191                 return;
192             case 0:
193                 syslog(L_NOTICE, "%s bad_auth %s (%s)", ClientHost, PERMuser,
194                         logrec);
195                 Reply("%d Authentication failed\r\n", NNTP_ACCESS_VAL);
196                 free(logrec);
197                 ExitWithStats(1, false);
198             default:
199                 /* lower level has issued Reply */
200                 return;
201         }
202
203     } else {
204
205         if (strcasecmp(av[1], "simple") == 0) {
206             if (ac != 4) {
207                 Reply("%d AUTHINFO SIMPLE <USER> <PASS>\r\n", NNTP_BAD_COMMAND_VAL);
208                 return;
209             }
210             strlcpy(User, av[2], sizeof(User));
211             strlcpy(Password, av[3], sizeof(Password));
212         } else {
213             if (strcasecmp(av[1], "user") == 0) {
214                 strlcpy(User, av[2], sizeof(User));
215                 Reply("%d PASS required\r\n", NNTP_AUTH_NEXT_VAL);
216                 return;
217             }
218
219             if (strcasecmp(av[1], "pass") != 0) {
220                 Reply("%d bad authinfo param\r\n", NNTP_BAD_COMMAND_VAL);
221                 return;
222             }
223             if (User[0] == '\0') {
224                 Reply("%d USER required\r\n", NNTP_AUTH_REJECT_VAL);
225                 return;
226             }
227
228             strlcpy(Password, av[2], sizeof(Password));
229         }
230
231         if (strcmp(User, PERMuser) == 0 && strcmp(Password, PERMpass) == 0) {
232             syslog(L_NOTICE, "%s user %s", ClientHost, PERMuser);
233             if (LLOGenable) {
234                 fprintf(locallog, "%s user (%s):%s\n", ClientHost, Username, PERMuser);
235                 fflush(locallog);
236             }
237             Reply("%d Ok\r\n", NNTP_AUTH_OK_VAL);
238             PERMneedauth = false;
239             PERMauthorized = true;
240             return;
241         }
242         
243         errorstr[0] = '\0';
244         
245         PERMlogin(User, Password, errorstr);
246         PERMgetpermissions();
247         if (!PERMneedauth) {
248             syslog(L_NOTICE, "%s user %s", ClientHost, PERMuser);
249             if (LLOGenable) {
250                 fprintf(locallog, "%s user (%s):%s\n", ClientHost, Username, PERMuser);
251                 fflush(locallog);
252             }
253             Reply("%d Ok\r\n", NNTP_AUTH_OK_VAL);
254             PERMneedauth = false;
255             PERMauthorized = true;
256             return;
257         }
258
259         syslog(L_NOTICE, "%s bad_auth", ClientHost);
260         if (errorstr[0] != '\0') {
261             syslog(L_NOTICE, "%s script error str: %s", ClientHost, errorstr);
262             Reply("%d %s\r\n", NNTP_ACCESS_VAL, errorstr);
263         } else {
264             Reply("%d Authentication error\r\n", NNTP_ACCESS_VAL);
265         }
266         ExitWithStats(1, false);
267     }
268
269 }
270
271
272 /*
273 **  The "DATE" command.  Part of NNTPv2.
274 */
275 /* ARGSUSED0 */
276 void
277 CMDdate(ac, av)
278     int         ac UNUSED;
279     char        *av[] UNUSED;
280 {
281     TIMEINFO    t;
282     struct tm   *gmt;
283
284     if (GetTimeInfo(&t) < 0 || (gmt = gmtime(&t.time)) == NULL) {
285         Reply("%d Can't get time, %s\r\n", NNTP_TEMPERR_VAL, strerror(errno));
286         return;
287     }
288     Reply("%d %04.4d%02.2d%02.2d%02.2d%02.2d%02.2d\r\n",
289         NNTP_DATE_FOLLOWS_VAL,
290         gmt->tm_year + 1900, gmt->tm_mon + 1, gmt->tm_mday,
291         gmt->tm_hour, gmt->tm_min, gmt->tm_sec);
292 }
293
294
295 /*
296 **  Handle the "mode" command.
297 */
298 /* ARGSUSED */
299 void
300 CMDmode(ac, av)
301     int         ac UNUSED;
302     char        *av[];
303 {
304     if (strcasecmp(av[1], "reader") == 0)
305         Reply("%d %s InterNetNews NNRP server %s ready (%s).\r\n",
306                PERMcanpost ? NNTP_POSTOK_VAL : NNTP_NOPOSTOK_VAL,
307                PERMaccessconf->pathhost, inn_version_string,
308                PERMcanpost ? "posting ok" : "no posting");
309     else
310         Reply("%d What?\r\n", NNTP_SYNTAX_VAL);
311 }
312
313 static int GroupCompare(const void *a1, const void* b1) {
314     const GROUPDATA     *a = a1;
315     const GROUPDATA     *b = b1;
316
317     return strcmp(a->name, b->name);
318 }
319
320 /*
321 **  Display new newsgroups since a given date and time for specified
322 **  <distributions>.
323 */
324 void CMDnewgroups(int ac, char *av[])
325 {
326     char                *p;
327     char                *q;
328     QIOSTATE            *qp;
329     time_t              date;
330     char                *grplist[2];
331     int                 hi, lo, count, flag;
332     GROUPDATA           *grouplist = NULL;
333     GROUPDATA           key;
334     GROUPDATA           *gd;
335     int                 listsize = 0;
336     int                 numgroups = 0;
337     int                 numfound = 0;
338     int                 i;
339     bool                local;
340
341     /* Parse the date. */
342     local = !(ac > 3 && strcasecmp(av[3], "GMT") == 0);
343     date = parsedate_nntp(av[1], av[2], local);
344     if (date == (time_t) -1) {
345         Reply("%d Bad date\r\n", NNTP_SYNTAX_VAL);
346         return;
347     }
348
349     /* Log an error if active.times doesn't exist, but don't return an error
350        to the client.  The most likely cause of this is a new server
351        installation that's yet to have any new groups created, and returning
352        an error was causing needless confusion.  Just return the empty list
353        of groups. */
354     if ((qp = QIOopen(ACTIVETIMES)) == NULL) {
355         syslog(L_ERROR, "%s cant fopen %s %m", ClientHost, ACTIVETIMES);
356         Reply("%d New newsgroups follow.\r\n", NNTP_NEWGROUPS_FOLLOWS_VAL);
357         Printf(".\r\n");
358         return;
359     }
360
361     /* Read the file, ignoring long lines. */
362     while ((p = QIOread(qp)) != NULL) {
363         if ((q = strchr(p, ' ')) == NULL)
364             continue;
365         *q++ = '\0';
366         if ((time_t) atol(q) < date)
367             continue;
368         if (!OVgroupstats(p, &lo, &hi, &count, &flag))
369             continue;
370
371         if (PERMspecified) {
372             grplist[0] = p;
373             grplist[1] = NULL;
374             if (!PERMmatch(PERMreadlist, grplist))
375                 continue;
376         }
377         else 
378             continue;
379
380         if (grouplist == NULL) {
381             grouplist = xmalloc(1000 * sizeof(GROUPDATA));
382             listsize = 1000;
383         }
384         if (listsize <= numgroups) {
385             listsize += 1000;
386             grouplist = xrealloc(grouplist, listsize * sizeof(GROUPDATA));
387         }
388
389         grouplist[numgroups].high = hi;
390         grouplist[numgroups].low = lo;
391         grouplist[numgroups].count = count;
392         grouplist[numgroups].name = xstrdup(p);
393         numgroups++;
394     }
395     QIOclose(qp);
396
397     if ((qp = QIOopen(ACTIVE)) == NULL) {
398         syslog(L_ERROR, "%s cant fopen %s %m", ClientHost, ACTIVE);
399         Reply("%d Cannot open active file.\r\n", NNTP_TEMPERR_VAL);
400         return;
401     }
402     qsort(grouplist, numgroups, sizeof(GROUPDATA), GroupCompare);
403     Reply("%d New newsgroups follow.\r\n", NNTP_NEWGROUPS_FOLLOWS_VAL);
404     for (numfound = numgroups; (p = QIOread(qp)) && numfound;) {
405         if ((q = strchr(p, ' ')) == NULL)
406             continue;
407         *q++ = '\0';
408         if ((q = strchr(q, ' ')) == NULL)
409             continue;
410         q++;
411         if ((q = strchr(q, ' ')) == NULL)
412             continue;
413         q++;
414         key.name = p;
415         if ((gd = bsearch(&key, grouplist, numgroups, sizeof(GROUPDATA), GroupCompare)) == NULL)
416             continue;
417         Printf("%s %u %u %s\r\n", p, gd->high, gd->low, q);
418         numfound--;
419     }
420     for (i = 0; i < numgroups; i++) {
421         free(grouplist[i].name);
422     }
423     free(grouplist);
424     QIOclose(qp);
425     Printf(".\r\n");
426 }
427
428
429 /*
430 **  Post an article.
431 */
432 /* ARGSUSED */
433 void
434 CMDpost(int ac UNUSED, char *av[] UNUSED)
435 {
436     static char *article;
437     static int  size;
438     char        *p, *q;
439     char        *end;
440     int         longline;
441     READTYPE    r;
442     int         i;
443     long        l;
444     long        sleeptime;
445     char        *path;
446     const char *response;
447     char        idbuff[SMBUF];
448     static int  backoff_inited = false;
449     bool        ihave, permanent;
450
451     ihave = (strcasecmp(av[0], "ihave") == 0);
452     if (ihave && (!PERMaccessconf->allowihave || !PERMcanpost)) {
453         syslog(L_NOTICE, "%s noperm ihave without permission", ClientHost);
454         Reply("%s\r\n", NNTP_ACCESS);
455         return;
456     }
457     if (!ihave && !PERMcanpost) {
458         syslog(L_NOTICE, "%s noperm post without permission", ClientHost);
459         Reply("%s\r\n", NNTP_CANTPOST);
460         return;
461     }
462
463     if (!backoff_inited) {
464         /* Exponential posting backoff */
465         InitBackoffConstants();
466         backoff_inited = true;
467     }
468
469     /* Dave's posting limiter - Limit postings to a certain rate
470      * And now we support multiprocess rate limits. Questions?
471      * Email dave@jetcafe.org.
472      */
473     if (BACKOFFenabled) {
474
475       /* Acquire lock (this could be in RateLimit but that would
476        * invoke the spaghetti factor). 
477        */
478       if ((path = (char *) PostRecFilename(ClientIpString,PERMuser)) == NULL) {
479         Reply("%s\r\n", NNTP_CANTPOST);
480         return;
481       }
482       
483       if (LockPostRec(path) == 0) {
484         syslog(L_ERROR, "%s Error write locking '%s'",
485                ClientHost, path);
486         Reply("%s\r\n", NNTP_CANTPOST);
487         return;
488       }
489       
490       if (!RateLimit(&sleeptime,path)) {
491         syslog(L_ERROR, "%s can't check rate limit info", ClientHost);
492         Reply("%s\r\n", NNTP_CANTPOST);
493         UnlockPostRec(path);
494         return;
495       } else if (sleeptime != 0L) {
496         syslog(L_NOTICE,"%s post sleep time is now %ld", ClientHost, sleeptime);
497         sleep(sleeptime);
498       }
499       
500       /* Remove the lock here so that only one nnrpd process does the
501        * backoff sleep at once. Other procs are sleeping for the lock.
502        */
503       UnlockPostRec(path);
504
505     } /* end backoff code */
506
507     /* Start at beginning of buffer. */
508     if (article == NULL) {
509         size = 4096;
510         article = xmalloc(size);
511     }
512     idbuff[0] = 0;
513     if (ihave) {
514         Reply(NNTP_SENDIT "\r\n");
515     } else {
516         if ((p = GenerateMessageID(PERMaccessconf->domain)) != NULL) {
517             if (VirtualPathlen > 0) {
518                 q = p;
519                 if ((p = strchr(p, '@')) != NULL) {
520                     *p = '\0';
521                     snprintf(idbuff, sizeof(idbuff), "%s%s@%s>", q,
522                              NNRPinstance, PERMaccessconf->domain);
523                 }
524             } else {
525                 strlcpy(idbuff, p, sizeof(idbuff));
526             }
527         }
528         Reply("%d Ok, recommended ID %s\r\n", NNTP_START_POST_VAL, idbuff);
529     }
530     fflush(stdout);
531
532     p = article;
533     end = &article[size];
534
535     longline = 0;
536     for (l = 1; ; l++) {
537         size_t len;
538         const char *line;
539
540         r = line_read(&NNTPline, PERMaccessconf->clienttimeout, &line, &len);
541         switch (r) {
542         default:
543             warn("%s internal %d in post", ClientHost, r);
544             /* FALLTHROUGH */
545         case RTtimeout:
546             warn("%s timeout in post", ClientHost);
547             ExitWithStats(1, false);
548             /* NOTREACHED */
549         case RTeof:
550             warn("%s eof in post", ClientHost);
551             ExitWithStats(1, false);
552             /* NOTREACHED */
553         case RTlong:
554             if (longline == 0)
555                 longline = l;
556             continue;
557         case RTok:
558             break;
559         }
560
561         /* if its the terminator, break out */
562         if (strcmp(line, ".") == 0) {
563             break;
564         }
565
566         /* if they broke our line length limit, there's little point
567          * in processing any more of their input */
568         if (longline != 0) {
569             continue;
570         }
571
572         /* +2 because of the \n\0 we append; note we don't add the 2
573          * when increasing the size of the buffer as ART_LINE_MALLOC
574          * will always be larger than 2 bytes */
575         if ((len + 2) > (size_t)(end - p)) {
576             i = p - article;
577             size += len + ART_LINE_MALLOC;
578             article = xrealloc(article, size);
579             end = &article[size];
580             p = i + article;
581         }
582
583         /* reverse any byte-stuffing */
584         if (*line == '.') {
585             ++line;
586             --len;
587         }
588         memcpy(p, line, len);
589         p += len;
590         *p++ = '\n';
591         *p = '\0';
592     }
593
594     if (longline) {
595         warn("%s toolong in post", ClientHost);
596         Printf("%d Line %d too long\r\n", 
597                ihave ? NNTP_REJECTIT_VAL : NNTP_POSTFAIL_VAL, longline);
598         POSTrejected++;
599         return;
600     }
601
602     /* Send the article to the server. */
603     response = ARTpost(article, idbuff, ihave, &permanent);
604     if (response == NULL) {
605         notice("%s post ok %s", ClientHost, idbuff);
606         Reply("%s %s\r\n", ihave ? NNTP_TOOKIT : NNTP_POSTEDOK, idbuff);
607         POSTreceived++;
608     }
609     else {
610         if ((p = strchr(response, '\r')) != NULL)
611             *p = '\0';
612         if ((p = strchr(response, '\n')) != NULL)
613             *p = '\0';
614         notice("%s post failed %s", ClientHost, response);
615         if (!ihave || permanent) {
616             /* for permanent errors reject the message */
617             Reply("%d %s\r\n", ihave ? NNTP_REJECTIT_VAL : NNTP_POSTFAIL_VAL,
618                   response);
619         } else {
620             /* non-permanent errors only have relevance to ihave, for
621              * these we have the error status from the upstream
622              * server to report */
623             Reply("%s\r\n", response);
624         }
625         POSTrejected++;
626     }
627 }
628
629 /*
630 **  The "xpath" command.  An uncommon extension.
631 */
632 /* ARGSUSED */
633 void
634 CMDxpath(ac, av)
635     int         ac UNUSED;
636     char        *av[] UNUSED;
637 {
638     Reply("%d Syntax error or bad command\r\n", NNTP_BAD_COMMAND_VAL);
639 }