1 /*
2 **  Miscellaneous commands.
3 */
4 
5 #include "portable/system.h"
6 
7 #include <sys/wait.h>
8 
9 #include "inn/fdflag.h"
10 #include "inn/innconf.h"
11 #include "inn/messages.h"
12 #include "inn/ov.h"
13 #include "inn/version.h"
14 #include "nnrpd.h"
15 #include "tls.h"
16 
17 
18 typedef struct {
19     char *name;
20     ARTNUM high;
21     ARTNUM low;
22     unsigned long count;
23 } GROUPDATA;
24 
25 
26 /*
27 **  Check after a successful authentication if the currently selected
28 **  newsgroup is still readable.  AUTHINFO SASL and STARTTLS do not need
29 **  it because the NNTP protocol is reset after it.
30 **
31 **  Return true if the group must be made invalid.
32 */
33 static bool
makeGroupInvalid(void)34 makeGroupInvalid(void)
35 {
36     bool hookpresent = false;
37     char *grplist[2];
38 
39     /* If no group has been selected yet, it is considered as valid. */
40     if (GRPcur == NULL) {
41         return false;
42     }
43 
44 #ifdef DO_PYTHON
45     hookpresent = PY_use_dynamic;
46     if (hookpresent) {
47         char *reply;
48 
49         /* Authorize user using Python module method dynamic. */
50         if (PY_dynamic(PERMuser, GRPcur, false, &reply) < 0) {
51             syslog(L_NOTICE, "PY_dynamic(): authorization skipped due to no "
52                              "Python dynamic method defined");
53         } else {
54             if (reply != NULL) {
55                 syslog(L_TRACE,
56                        "PY_dynamic() returned a refuse string for user %s at "
57                        "%s who wants to read %s: %s",
58                        PERMuser, Client.host, GRPcur, reply);
59                 free(reply);
60                 return true;
61             }
62         }
63     }
64 #endif /* DO_PYTHON */
65 
66     if (!hookpresent) {
67         if (PERMspecified) {
68             grplist[0] = GRPcur;
69             grplist[1] = NULL;
70             if (!PERMmatch(PERMreadlist, grplist)) {
71                 return true;
72             }
73         } else {
74             return true;
75         }
76     }
77 
78     if (!hookpresent && !PERMcanread) {
79         return true;
80     }
81 
82     return false;
83 }
84 
85 
86 /*  Returns:
87 **    -1 for problem (such as no such authenticator, etc.).
88 **     1 for authentication succeeded.
89 **     0 for authentication failed.
90 */
91 static char *PERMauthstring;
92 
93 static int
PERMgeneric(char * av[],char * accesslist,size_t size)94 PERMgeneric(char *av[], char *accesslist, size_t size)
95 {
96     char path[BIG_BUFFER], *fields[6], *p;
97     size_t j;
98     int i, pan[2], status;
99     pid_t pid;
100     struct stat stb;
101 
102     av += 2;
103 
104     PERMcanread = false;
105     PERMcanpost = false;
106     PERMaccessconf->locpost = false;
107     PERMaccessconf->allowapproved = false;
108     PERMaccessconf->allowihave = false;
109     PERMaccessconf->allownewnews = false;
110 
111     if (!*av) {
112         Reply("%d No authenticator provided\r\n", NNTP_ERR_SYNTAX);
113         return -1;
114     }
115 
116     /* Check for ".." (not "../").  Path must not be changed! */
117     if (strstr(av[0], "..") != NULL) {
118         Reply("%d .. in authenticator %s\r\n", NNTP_ERR_SYNTAX, av[0]);
119         return -1;
120     }
121 
122     /* 502 if authentication will fail. */
123     if (!PERMcanauthenticate) {
124         if (PERMauthorized && !PERMneedauth)
125             Reply("%d Already authenticated\r\n", NNTP_ERR_ACCESS);
126         else
127             Reply("%d Authentication will fail\r\n", NNTP_ERR_ACCESS);
128         return -1;
129     }
130 
131     if (strchr(INN_PATH_AUTHDIR, '/') == NULL)
132         snprintf(path, sizeof(path), "%s/%s/%s/%s", innconf->pathbin,
133                  INN_PATH_AUTHDIR, INN_PATH_AUTHDIR_GENERIC, av[0]);
134     else
135         snprintf(path, sizeof(path), "%s/%s/%s", INN_PATH_AUTHDIR,
136                  INN_PATH_AUTHDIR_GENERIC, av[0]);
137 
138 #if !defined(S_IXUSR) && defined(_S_IXUSR)
139 #    define S_IXUSR _S_IXUSR
140 #endif /* !defined(S_IXUSR) && defined(_S_IXUSR) */
141 
142 #if !defined(S_IXUSR) && defined(S_IEXEC)
143 #    define S_IXUSR S_IEXEC
144 #endif /* !defined(S_IXUSR) && defined(S_IEXEC) */
145 
146     if (stat(path, &stb) || !(stb.st_mode & S_IXUSR)) {
147         Reply("%d No such authenticator %s\r\n", NNTP_ERR_UNAVAILABLE, av[0]);
148         return -1;
149     }
150 
151     /* Create a pipe. */
152     if (pipe(pan) < 0) {
153         Reply("%d Can't pipe %s\r\n", NNTP_FAIL_ACTION, strerror(errno));
154         syslog(L_FATAL, "can't pipe for %s %m", av[0]);
155         return -1;
156     }
157 
158     for (i = 0; (pid = fork()) < 0; i++) {
159         if (i == (long) innconf->maxforks) {
160             Reply("%d Can't fork %s\r\n", NNTP_FAIL_ACTION, strerror(errno));
161             syslog(L_FATAL, "can't fork %s %m", av[0]);
162             return -1;
163         }
164         syslog(L_NOTICE, "can't fork %s -- waiting", av[0]);
165         sleep(5);
166     }
167 
168     /* Run the child, with redirection. */
169     if (pid == 0) {
170         close(STDERR_FILENO); /* Close existing stderr. */
171         close(pan[PIPE_READ]);
172 
173         /* stderr goes down the pipe. */
174         if (pan[PIPE_WRITE] != STDERR_FILENO) {
175             if ((i = dup2(pan[PIPE_WRITE], STDERR_FILENO)) != STDERR_FILENO) {
176                 syslog(L_FATAL, "can't dup2 %d to %d got %d %m",
177                        pan[PIPE_WRITE], STDERR_FILENO, i);
178                 _exit(1);
179             }
180             close(pan[PIPE_WRITE]);
181         }
182 
183         fdflag_close_exec(STDIN_FILENO, false);
184         fdflag_close_exec(STDOUT_FILENO, false);
185         fdflag_close_exec(STDERR_FILENO, false);
186 
187         execv(path, av);
188         /* RFC 2980 requires 500 if there are unspecified errors during
189          * the execution of the provided program. */
190         Reply("%d Program error occurred\r\n", NNTP_ERR_COMMAND);
191 
192         syslog(L_FATAL, "can't execv %s %m", path);
193         _exit(1);
194     }
195 
196     close(pan[PIPE_WRITE]);
197     if (read(pan[PIPE_READ], path, sizeof(path)) < 0) {
198         syslog(L_FATAL, "can't read %s %m", path);
199         return 0;
200     }
201 
202     waitpid(pid, &status, 0);
203     if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
204         return 0;
205 
206     if ((p = strchr(path, '\n')) != NULL)
207         *p = '\0';
208 
209     if (PERMauthstring)
210         free(PERMauthstring);
211 
212     PERMauthstring = xstrdup(path);
213 
214     // syslog(L_NOTICE, "%s (%ld) returned: %d %s %d\n", av[0], (long) pid, i,
215     // path, status);
216     /* Split "host:permissions:user:pass:groups" into fields. */
217     for (fields[0] = path, j = 0, p = path; *p; p++)
218         if (*p == ':') {
219             *p = '\0';
220             ++j;
221             if (j < ARRAY_SIZE(fields)) {
222                 fields[j] = p + 1;
223             } else {
224                 Reply("%d Program error occurred\r\n", NNTP_FAIL_ACTION);
225                 syslog(L_FATAL, "over-long response from %s", av[0]);
226                 return -1;
227             }
228         }
229 
230     if (j < 4) {
231         Reply("%d Program error occurred\r\n", NNTP_FAIL_ACTION);
232         syslog(L_FATAL, "short response from %s", av[0]);
233         return -1;
234     }
235 
236     PERMcanread = strchr(fields[1], 'R') != NULL;
237     PERMcanpost = strchr(fields[1], 'P') != NULL;
238     PERMaccessconf->allowapproved = strchr(fields[1], 'A') != NULL;
239     PERMaccessconf->locpost = strchr(fields[1], 'L') != NULL;
240     PERMaccessconf->allowihave = strchr(fields[1], 'I') != NULL;
241     PERMaccessconf->allownewnews = strchr(fields[1], 'N') != NULL;
242     strlcpy(PERMuser, fields[2], sizeof(PERMuser));
243     strlcat(PERMuser, "@", sizeof(PERMuser));
244     strlcat(PERMuser, fields[0], sizeof(PERMuser));
245     // strlcpy(PERMpass, fields[3], sizeof(PERMpass));
246     strlcpy(accesslist, fields[4], size);
247 
248     // for (i = 0; fields[i] && i < 5; i++)
249     //    syslog(L_NOTICE, "fields[%d] = %s\n", i, fields[i]);
250 
251     return 1;
252 }
253 
254 
255 /*
256 **  The AUTHINFO command.
257 */
258 void
CMDauthinfo(int ac,char * av[])259 CMDauthinfo(int ac, char *av[])
260 {
261     static char User[SMBUF];
262     static char Password[SMBUF];
263     /* XXX BIG_BUFFER, if changed, should also be changed in perl.c and
264      * python.c. */
265     char accesslist[BIG_BUFFER];
266     char errorstr[BIG_BUFFER];
267     int code;
268 
269 #if defined(HAVE_ZLIB)
270     /* If a compression layer is active, AUTHINFO is not possible. */
271     if (compression_layer_on && !tls_compression_on) {
272         Reply("%d Already using a compression layer\r\n", NNTP_ERR_ACCESS);
273         return;
274     }
275 #endif
276 
277     if (strcasecmp(av[1], "GENERIC") == 0) {
278         char *logrec = Glom(av);
279 
280         /* Go on parsing the command line. */
281         ac--;
282         (void) reArgify(av[ac], &av[ac], -1, true);
283 
284         strlcpy(PERMuser, "<none>", sizeof(PERMuser));
285 
286         /* Arguments are checked by PERMgeneric(). */
287         switch (PERMgeneric(av, accesslist, sizeof(accesslist))) {
288         case 1:
289             PERMspecified = NGgetlist(&PERMreadlist, accesslist);
290             PERMpostlist = PERMreadlist;
291             syslog(L_NOTICE, "%s auth %s (%s -> %s)", Client.host, PERMuser,
292                    logrec, PERMauthstring ? PERMauthstring : "");
293             Reply("%d Authentication succeeded\r\n", NNTP_OK_AUTHINFO);
294             PERMneedauth = false;
295             PERMauthorized = true;
296             PERMcanauthenticate = false;
297             PERMgroupmadeinvalid = makeGroupInvalid();
298             free(logrec);
299             return;
300         case 0:
301             syslog(L_NOTICE, "%s bad_auth %s (%s)", Client.host, PERMuser,
302                    logrec);
303             /* We keep the right 481 code here instead of the wrong 502
304              * answer suggested in RFC 2080. */
305             Reply("%d Authentication failed\r\n", NNTP_FAIL_AUTHINFO_BAD);
306             free(logrec);
307             return;
308         default:
309             /* Lower level (-1) has already issued a reply. */
310             return;
311         }
312     } else if (strcasecmp(av[1], "SASL") == 0) {
313 #ifdef HAVE_SASL
314         /* Go on parsing the command line. */
315         ac--;
316         ac += reArgify(av[ac], &av[ac], -1, true);
317 
318         /* Arguments are checked by SASLauth(). */
319         SASLauth(ac, av);
320 #else
321         Reply("%d SASL authentication unsupported\r\n", NNTP_ERR_SYNTAX);
322         return;
323 #endif /* HAVE_SASL */
324     } else {
325         /* Each time AUTHINFO USER is used, the new username is cached. */
326         if (strcasecmp(av[1], "USER") == 0) {
327             /* 502 if authentication will fail. */
328             if (!PERMcanauthenticate) {
329                 if (PERMauthorized && !PERMneedauth)
330                     Reply("%d Already authenticated\r\n", NNTP_ERR_ACCESS);
331                 else
332                     Reply("%d Authentication will fail\r\n", NNTP_ERR_ACCESS);
333                 return;
334             }
335 
336 #ifdef HAVE_OPENSSL
337             /* Check whether STARTTLS must be used before trying to
338              * authenticate. */
339             if (PERMcanauthenticate && !PERMcanauthenticatewithoutSSL
340                 && !encryption_layer_on) {
341                 Reply("%d Encryption required\r\n", NNTP_FAIL_PRIVACY_NEEDED);
342                 return;
343             }
344 #endif
345 
346             strlcpy(User, av[2], sizeof(User));
347             Reply("%d Enter password\r\n", NNTP_CONT_AUTHINFO);
348             return;
349         }
350 
351         /* If it is not AUTHINFO PASS, we do not support the provided
352          * subcommand. */
353         if (strcasecmp(av[1], "PASS") != 0) {
354             Reply("%d Bad AUTHINFO param\r\n", NNTP_ERR_SYNTAX);
355             return;
356         }
357 
358         /* 502 if authentication will fail. */
359         if (!PERMcanauthenticate) {
360             if (PERMauthorized && !PERMneedauth)
361                 Reply("%d Already authenticated\r\n", NNTP_ERR_ACCESS);
362             else
363                 Reply("%d Authentication will fail\r\n", NNTP_ERR_ACCESS);
364             return;
365         }
366 
367 #ifdef HAVE_OPENSSL
368         /* Check whether STARTTLS must be used before trying to authenticate.
369          */
370         if (PERMcanauthenticate && !PERMcanauthenticatewithoutSSL
371             && !encryption_layer_on) {
372             Reply("%d Encryption required\r\n", NNTP_FAIL_PRIVACY_NEEDED);
373             return;
374         }
375 #endif
376 
377         /* AUTHINFO PASS cannot be sent before AUTHINFO USER. */
378         if (User[0] == '\0') {
379             Reply("%d Authentication commands issued out of sequence\r\n",
380                   NNTP_FAIL_AUTHINFO_REJECT);
381             return;
382         }
383 
384         /* There is a cached username and a password is provided. */
385         strlcpy(Password, av[2], sizeof(Password));
386 
387         errorstr[0] = '\0';
388         code = NNTP_FAIL_AUTHINFO_BAD;
389 
390         PERMlogin(User, Password, &code, errorstr);
391         PERMgetpermissions();
392 
393         /* If authentication is successful. */
394         if (!PERMneedauth) {
395             syslog(L_NOTICE, "%s user %s", Client.host, PERMuser);
396             if (LLOGenable) {
397                 fprintf(locallog, "%s user (%s):%s\n", Client.host, Username,
398                         PERMuser);
399                 fflush(locallog);
400             }
401             Reply("%d Authentication succeeded\r\n", NNTP_OK_AUTHINFO);
402             PERMneedauth = false;
403             PERMauthorized = true;
404             PERMcanauthenticate = false;
405             PERMgroupmadeinvalid = makeGroupInvalid();
406             return;
407         }
408 
409         /* For backwards compatibility, we return 481 instead of 502 (which had
410          * the same meaning as 481 in RFC 2980). */
411         if (code == NNTP_ERR_ACCESS) {
412             code = NNTP_FAIL_AUTHINFO_BAD;
413         }
414 
415         syslog(L_NOTICE, "%s bad_auth", Client.host);
416         /* Return 403 in case the return code is not 481. */
417         if (errorstr[0] != '\0') {
418             syslog(L_NOTICE, "%s script error str: %s", Client.host, errorstr);
419             Reply("%d %s\r\n",
420                   code != NNTP_FAIL_AUTHINFO_BAD ? NNTP_FAIL_ACTION : code,
421                   errorstr);
422         } else {
423             Reply("%d Authentication failed\r\n",
424                   code != NNTP_FAIL_AUTHINFO_BAD ? NNTP_FAIL_ACTION : code);
425         }
426     }
427 }
428 
429 
430 /*
431 **  The DATE command.  Useful mainly in conjunction with NEWNEWS.
432 */
433 void
CMDdate(int ac UNUSED,char * av[]UNUSED)434 CMDdate(int ac UNUSED, char *av[] UNUSED)
435 {
436     time_t now;
437     struct tm *gmt;
438 
439     now = time(NULL);
440     gmt = gmtime(&now);
441     if (now == (time_t) -1 || gmt == NULL) {
442         Reply("%d Can't get time, %s\r\n", NNTP_FAIL_ACTION, strerror(errno));
443         return;
444     }
445     Reply("%d %04d%02d%02d%02d%02d%02d\r\n", NNTP_INFO_DATE,
446           gmt->tm_year + 1900, gmt->tm_mon + 1, gmt->tm_mday, gmt->tm_hour,
447           gmt->tm_min, gmt->tm_sec);
448 }
449 
450 
451 /*
452 **  Handle the MODE command.
453 **  Note that MODE STREAM must return 501 as an unknown MODE variant
454 **  because nnrpd does not implement STREAMING.
455 */
456 void
CMDmode(int ac UNUSED,char * av[])457 CMDmode(int ac UNUSED, char *av[])
458 {
459     if (strcasecmp(av[1], "READER") == 0)
460         /* In the case AUTHINFO has already been successfully used,
461          * nnrpd must answer as a no-op (it still advertises the READER
462          * capability but not MODE-READER). */
463         Reply("%d %s InterNetNews NNRP server %s ready (%s)\r\n",
464               (PERMcanpost || (PERMcanauthenticate && PERMcanpostgreeting))
465                   ? NNTP_OK_BANNER_POST
466                   : NNTP_OK_BANNER_NOPOST,
467               PERMaccessconf->pathhost, INN_VERSION_STRING,
468               (!PERMneedauth && PERMcanpost) ? "posting ok" : "no posting");
469     else
470         Reply("%d Unknown MODE variant\r\n", NNTP_ERR_SYNTAX);
471 }
472 
473 
474 static int
GroupCompare(const void * a1,const void * b1)475 GroupCompare(const void *a1, const void *b1)
476 {
477     const GROUPDATA *a = a1;
478     const GROUPDATA *b = b1;
479 
480     return strcmp(a->name, b->name);
481 }
482 
483 
484 /*
485 **  Display new newsgroups since a given date and time.
486 */
487 void
CMDnewgroups(int ac,char * av[])488 CMDnewgroups(int ac, char *av[])
489 {
490     char *p;
491     char *q;
492     QIOSTATE *qp;
493     time_t date;
494     char *grplist[2];
495     int hi, lo, count, flag;
496     GROUPDATA *grouplist = NULL;
497     GROUPDATA key;
498     GROUPDATA *gd;
499     int listsize = 0;
500     int numgroups = 0;
501     int numfound = 0;
502     int i;
503     bool local = true;
504 
505     /* Check the arguments and parse the date. */
506     if (ac > 3) {
507         if (strcasecmp(av[3], "GMT") == 0)
508             local = false;
509         else {
510             Reply("%d Syntax error for \"GMT\"\r\n", NNTP_ERR_SYNTAX);
511             return;
512         }
513     }
514     date = parsedate_nntp(av[1], av[2], local);
515     if (date == (time_t) -1) {
516         Reply("%d Bad date\r\n", NNTP_ERR_SYNTAX);
517         return;
518     }
519 
520     /* Log an error if active.times doesn't exist, but don't return an error
521        to the client.  The most likely cause of this is a new server
522        installation that's yet to have any new groups created, and returning
523        an error was causing needless confusion.  Just return the empty list
524        of groups. */
525     if ((qp = QIOopen(ACTIVETIMES)) == NULL) {
526         syslog(L_ERROR, "%s can't fopen %s %m", Client.host, ACTIVETIMES);
527         Reply("%d No new newsgroups\r\n", NNTP_OK_NEWGROUPS);
528         Printf(".\r\n");
529         return;
530     }
531 
532     /* Read the file, ignoring long lines. */
533     while ((p = QIOread(qp)) != NULL) {
534         if ((q = strchr(p, ' ')) == NULL)
535             continue;
536         *q++ = '\0';
537         if ((time_t) atol(q) < date)
538             continue;
539         if (!OVgroupstats(p, &lo, &hi, &count, &flag))
540             continue;
541 
542         if (PERMspecified) {
543             grplist[0] = p;
544             grplist[1] = NULL;
545             if (!PERMmatch(PERMreadlist, grplist))
546                 continue;
547         } else
548             continue;
549 
550         if (grouplist == NULL) {
551             grouplist = xmalloc(1000 * sizeof(GROUPDATA));
552             listsize = 1000;
553         }
554         if (listsize <= numgroups) {
555             listsize += 1000;
556             grouplist = xrealloc(grouplist, listsize * sizeof(GROUPDATA));
557         }
558 
559         grouplist[numgroups].high = hi;
560         grouplist[numgroups].low = lo;
561         grouplist[numgroups].count = count;
562         grouplist[numgroups].name = xstrdup(p);
563         numgroups++;
564     }
565     QIOclose(qp);
566 
567     if ((qp = QIOopen(ACTIVE)) == NULL) {
568         syslog(L_ERROR, "%s can't fopen %s %m", Client.host, ACTIVE);
569         Reply("%d Can't open active file\r\n", NNTP_FAIL_ACTION);
570 
571         for (i = 0; i < numgroups; i++) {
572             free(grouplist[i].name);
573         }
574         free(grouplist);
575 
576         return;
577     }
578     qsort(grouplist, numgroups, sizeof(GROUPDATA), GroupCompare);
579     Reply("%d New newsgroups follow\r\n", NNTP_OK_NEWGROUPS);
580     for (numfound = numgroups; (p = QIOread(qp)) && numfound;) {
581         /* p will contain the name of the newsgroup.
582          * When the syntax of the line is not correct, we continue
583          * with the following line. */
584         if ((q = strchr(p, ' ')) == NULL)
585             continue;
586         *q++ = '\0';
587         /* Find out the end of the high water mark. */
588         if ((q = strchr(q, ' ')) == NULL)
589             continue;
590         q++;
591         /* Find out the end of the low water mark.
592          * q will contain the flag of the newsgroup. */
593         if ((q = strchr(q, ' ')) == NULL)
594             continue;
595         q++;
596         key.name = p;
597         if ((gd = bsearch(&key, grouplist, numgroups, sizeof(GROUPDATA),
598                           GroupCompare))
599             == NULL)
600             continue;
601         Printf("%s %lu %lu %s\r\n", p, gd->high, gd->low, q);
602         numfound--;
603     }
604     for (i = 0; i < numgroups; i++) {
605         free(grouplist[i].name);
606     }
607     free(grouplist);
608     QIOclose(qp);
609     Printf(".\r\n");
610 }
611 
612 
613 /*
614 **  Handle the POST and IHAVE commands.
615 */
616 void
CMDpost(int ac,char * av[])617 CMDpost(int ac, char *av[])
618 {
619     static char *article;
620     static int size;
621     char *p, *q;
622     char *end;
623     int longline;
624     READTYPE r;
625     int i;
626     long l;
627     long sleeptime;
628     char *path;
629     const char *response;
630     char idbuff[SMBUF];
631     static int backoff_inited = false;
632     bool ihave, permanent;
633 
634     ihave = (strcasecmp(av[0], "IHAVE") == 0);
635 
636     /* Check whether the Message-ID is valid for IHAVE. */
637     if (ihave && ac == 2 && !IsValidMessageID(av[1], true, laxmid)) {
638         Reply("%d Syntax error in Message-ID\r\n", NNTP_ERR_SYNTAX);
639         return;
640     }
641 
642     /* Check authorizations. */
643     if (ihave && (!PERMaccessconf->allowihave || !PERMcanpost)) {
644         syslog(L_NOTICE, "%s noperm ihave without permission", Client.host);
645         Reply("%d IHAVE command disabled by administrator\r\n",
646               PERMcanauthenticate ? NNTP_FAIL_AUTH_NEEDED : NNTP_ERR_ACCESS);
647         return;
648     }
649     if (!ihave && !PERMcanpost) {
650         syslog(L_NOTICE, "%s noperm post without permission", Client.host);
651         Reply("%d Posting not allowed\r\n",
652               PERMcanauthenticate && PERMcanpostgreeting
653                   ? NNTP_FAIL_AUTH_NEEDED
654                   : NNTP_FAIL_POST_AUTH);
655         return;
656     }
657 
658     if (!backoff_inited) {
659         /* Exponential posting backoff. */
660         InitBackoffConstants();
661         backoff_inited = true;
662     }
663 
664     /* Dave's posting limiter.  Limit postings to a certain rate.
665      * And now we support multiprocess rate limits.  Questions?
666      * E-mail <dave@jetcafe.org>. */
667     if (BACKOFFenabled) {
668         /* Acquire lock (this could be in RateLimit but that would
669          * invoke the spaghetti factor). */
670         if ((path = (char *) PostRecFilename(Client.ip, PERMuser)) == NULL) {
671             Reply("%d Retry later\r\n",
672                   ihave ? NNTP_FAIL_IHAVE_DEFER : NNTP_FAIL_POST_AUTH);
673             return;
674         }
675 
676         if (LockPostRec(path) == 0) {
677             syslog(L_ERROR, "%s error write locking '%s'", Client.host, path);
678             Reply("%d Retry later\r\n",
679                   ihave ? NNTP_FAIL_IHAVE_DEFER : NNTP_FAIL_POST_AUTH);
680             return;
681         }
682 
683         if (!RateLimit(&sleeptime, path)) {
684             syslog(L_ERROR, "%s can't check rate limit info", Client.host);
685             Reply("%d Retry later\r\n",
686                   ihave ? NNTP_FAIL_IHAVE_DEFER : NNTP_FAIL_POST_AUTH);
687             UnlockPostRec(path);
688             return;
689         } else if (sleeptime != 0L) {
690             syslog(L_NOTICE, "%s post sleep time is now %ld", Client.host,
691                    sleeptime);
692             sleep(sleeptime);
693         }
694 
695         /* Remove the lock here so that only one nnrpd process does the
696          * backoff sleep at once.  Other procs are sleeping for the lock. */
697         UnlockPostRec(path);
698     } /* End backoff code. */
699 
700     /* Start at beginning of buffer. */
701     if (article == NULL) {
702         size = 4096;
703         article = xmalloc(size);
704     }
705     idbuff[0] = 0;
706     if (ihave) {
707         /* Check whether it is a duplicate. */
708         if (History == NULL) {
709             time_t statinterval = 30;
710             History = HISopen(HISTORY, innconf->hismethod, HIS_RDONLY);
711             if (!History) {
712                 syslog(L_NOTICE, "can't initialize history");
713                 Reply("%d NNTP server unavailable; try later\r\n",
714                       NNTP_FAIL_TERMINATING);
715                 ExitWithStats(1, true);
716             }
717             HISctl(History, HISCTLS_STATINTERVAL, &statinterval);
718         }
719         if (HIScheck(History, av[1])) {
720             Reply("%d Duplicate\r\n", NNTP_FAIL_IHAVE_REFUSE);
721             return;
722         } else {
723             Reply("%d Send it; end with <CR-LF>.<CR-LF>\r\n", NNTP_CONT_IHAVE);
724         }
725     } else {
726         if ((p = GenerateMessageID(PERMaccessconf->domain)) != NULL) {
727             if (VirtualPathlen > 0) {
728                 q = p;
729                 if ((p = strchr(p, '@')) != NULL) {
730                     *p = '\0';
731                     snprintf(idbuff, sizeof(idbuff), "%s%s@%s>", q,
732                              NNRPinstance, PERMaccessconf->domain);
733                 }
734             } else {
735                 strlcpy(idbuff, p, sizeof(idbuff));
736             }
737         }
738         Reply("%d Ok, recommended Message-ID %s\r\n", NNTP_CONT_POST, idbuff);
739     }
740     fflush(stdout);
741 
742     p = article;
743     end = &article[size];
744 
745     longline = 0;
746     for (l = 1;; l++) {
747         size_t len;
748         const char *line;
749 
750         r = line_read(&NNTPline, PERMaccessconf->clienttimeout, &line, &len,
751                       NULL);
752         switch (r) {
753         default:
754             warn("%s internal %d in post", Client.host, r);
755             goto fallthrough;
756         case RTtimeout:
757         fallthrough:
758             warn("%s timeout in post", Client.host);
759             ExitWithStats(1, false);
760             /* NOTREACHED */
761         case RTeof:
762             warn("%s EOF in post", Client.host);
763             ExitWithStats(1, false);
764             /* NOTREACHED */
765         case RTlong:
766             if (longline == 0)
767                 longline = l;
768             continue;
769         case RTok:
770             break;
771         }
772 
773         /* If it is the terminator, break out. */
774         if (strcmp(line, ".") == 0) {
775             break;
776         }
777 
778         /* If they broke our line length limit, there's little point
779          * in processing any more of their input. */
780         if (longline != 0) {
781             continue;
782         }
783 
784         /* +2 because of the \n\0 we append; note we don't add the 2
785          * when increasing the size of the buffer as ART_LINE_MALLOC
786          * will always be larger than 2 bytes. */
787         if ((len + 2) > (size_t)(end - p)) {
788             i = p - article;
789             size += len + 4096;
790             article = xrealloc(article, size);
791             end = &article[size];
792             p = i + article;
793         }
794 
795         /* Reverse any byte-stuffing. */
796         if (*line == '.') {
797             ++line;
798             --len;
799         }
800         memcpy(p, line, len);
801         p += len;
802         *p++ = '\n';
803         *p = '\0';
804     }
805 
806     if (longline) {
807         warn("%s too long in post", Client.host);
808         Reply("%d Line %d too long\r\n",
809               ihave ? NNTP_FAIL_IHAVE_REJECT : NNTP_FAIL_POST_REJECT,
810               longline);
811         POSTrejected++;
812         return;
813     }
814 
815     /* Send the article to the server. */
816     response = ARTpost(article, idbuff, &permanent);
817     if (response == NULL) {
818         notice("%s %s ok %s", Client.host, ihave ? "ihave" : "post", idbuff);
819         Reply("%d Article received %s\r\n",
820               ihave ? NNTP_OK_IHAVE : NNTP_OK_POST, idbuff);
821         POSTreceived++;
822     } else {
823         if ((p = strchr(response, '\r')) != NULL)
824             *p = '\0';
825         if ((p = strchr(response, '\n')) != NULL)
826             *p = '\0';
827         notice("%s %s failed %s", Client.host, ihave ? "ihave" : "post",
828                response);
829         if (!ihave || permanent) {
830             /* For permanent errors, reject the message. */
831             Reply("%d %s\r\n",
832                   ihave ? NNTP_FAIL_IHAVE_REJECT : NNTP_FAIL_POST_REJECT,
833                   response);
834         } else {
835             /* Non-permanent errors only have relevance to IHAVE, for
836              * these we have the error status from the upstream
837              * server to report.  It includes the answer code. */
838             Reply("%s\r\n", response);
839         }
840         POSTrejected++;
841     }
842 }
843