1 /*
2 **  Miscellaneous support routines.
3 */
4 
5 #include "portable/system.h"
6 
7 /* Needed on AIX 4.1 to get fd_set and friends. */
8 #ifdef HAVE_SYS_SELECT_H
9 #    include <sys/select.h>
10 #endif
11 
12 #include "inn/innconf.h"
13 #include "nnrpd.h"
14 #include "tls.h"
15 
16 /* Outside the ifdef so that make depend works even ifndef HAVE_OPENSSL. */
17 #include "inn/ov.h"
18 
19 
20 /*
21 **  Match a list of newsgroup specifiers against a list of newsgroups.
22 **  func is called to see if there is a match.
23 */
24 bool
PERMmatch(char ** Pats,char ** list)25 PERMmatch(char **Pats, char **list)
26 {
27     int i;
28     char *p;
29     int match = false;
30 
31     if (Pats == NULL || Pats[0] == NULL)
32         return true;
33 
34     for (; *list; list++) {
35         for (i = 0; (p = Pats[i]) != NULL; i++) {
36             if (p[0] == '!') {
37                 if (uwildmat(*list, ++p))
38                     match = false;
39             } else if (uwildmat(*list, p))
40                 match = true;
41         }
42         if (match)
43             /* If we can read it in one group, we can read it, period. */
44             return true;
45     }
46 
47     return false;
48 }
49 
50 
51 /*
52 **  Check to see if user is allowed to see this article by matching
53 **  Xref (or Newsgroups) header field body.
54 */
55 bool
PERMartok(void)56 PERMartok(void)
57 {
58     static char **grplist;
59     char *p, **grp;
60 
61     if (!PERMspecified)
62         return false;
63 
64     if ((p = GetHeader("Xref", true)) == NULL) {
65         /* In case article does not include an Xref header field. */
66         if ((p = GetHeader("Newsgroups", true)) != NULL) {
67             if (!NGgetlist(&grplist, p))
68                 /* No newgroups or null entry. */
69                 return true;
70         } else {
71             return true;
72         }
73     } else {
74         /* Skip path element. */
75         if ((p = strchr(p, ' ')) == NULL)
76             return true;
77         for (p++; *p == ' '; p++)
78             ;
79         if (*p == '\0')
80             return true;
81         if (!NGgetlist(&grplist, p))
82             /* No newgroups or null entry. */
83             return true;
84         /* Chop ':' and article number. */
85         for (grp = grplist; *grp != NULL; grp++) {
86             if ((p = strchr(*grp, ':')) == NULL)
87                 return true;
88             *p = '\0';
89         }
90     }
91 
92 #ifdef DO_PYTHON
93     if (PY_use_dynamic) {
94         char *reply;
95 
96         /* Authorize user at a Python authorization module. */
97         if (PY_dynamic(PERMuser, p, false, &reply) < 0) {
98             syslog(L_NOTICE, "PY_dynamic(): authorization skipped due to no "
99                              "Python dynamic method defined");
100         } else {
101             if (reply != NULL) {
102                 syslog(L_TRACE,
103                        "PY_dynamic() returned a refuse string for user %s at "
104                        "%s who wants to read %s: %s",
105                        PERMuser, Client.host, p, reply);
106                 free(reply);
107                 return false;
108             }
109             return true;
110         }
111     }
112 #endif /* DO_PYTHON */
113 
114     return PERMmatch(PERMreadlist, grplist);
115 }
116 
117 
118 /*
119 **  Parse a newsgroups line, return true if there were any.
120 */
121 bool
NGgetlist(char *** argvp,char * list)122 NGgetlist(char ***argvp, char *list)
123 {
124     char *p;
125 
126     for (p = list; *p; p++)
127         if (*p == ',')
128             *p = ' ';
129 
130     return Argify(list, argvp) != 0;
131 }
132 
133 
134 /*********************************************************************
135  * POSTING RATE LIMITS -- The following code implements posting rate
136  * limits.  News clients are indexed by IP number (or PERMuser, see
137  * config file).  After a relatively configurable number of posts, the nnrpd
138  * process will sleep for a period of time before posting anything.
139  *
140  * Each time that IP number posts a message, the time of
141  * posting and the previous sleep time is stored.  The new sleep time
142  * is computed based on these values.
143  *
144  * To compute the new sleep time, the previous sleep time is, for most
145  * cases multiplied by a factor (backoff_k).
146  *
147  * See inn.conf(5) for how this code works.
148  *
149  *********************************************************************/
150 
151 /* Defaults are pass through, i.e. not enabled .
152  * NEW for INN 1.8 -- Use the inn.conf file to specify the following:
153  *
154  * backoffk: <integer>
155  * backoffpostfast: <integer>
156  * backoffpostslow: <integer>
157  * backofftrigger: <integer>
158  * backoffdb: <path>
159  * backoffauth: <true|false>
160  *
161  * You may also specify posting backoffs on a per user basis.  To do this,
162  * turn on backoffauth.
163  *
164  * Now these are runtime constants. <grin>
165  */
166 static char postrec_dir[SMBUF]; /* Where is the post record directory? */
167 
168 void
InitBackoffConstants(void)169 InitBackoffConstants(void)
170 {
171     struct stat st;
172 
173     /* Default is not to enable this code. */
174     BACKOFFenabled = false;
175 
176     /* Read the runtime config file to get parameters. */
177 
178     if ((PERMaccessconf->backoff_db == NULL)
179         || !(PERMaccessconf->backoff_postslow >= 1L))
180         return;
181 
182     /* Need this database for backing off. */
183     strlcpy(postrec_dir, PERMaccessconf->backoff_db, sizeof(postrec_dir));
184     if (stat(postrec_dir, &st) < 0) {
185         if (ENOENT == errno) {
186             if (!MakeDirectory(postrec_dir, true)) {
187                 syslog(L_ERROR, "%s cannot create backoff_db '%s': %s",
188                        Client.host, postrec_dir, strerror(errno));
189                 return;
190             }
191         } else {
192             syslog(L_ERROR, "%s cannot stat backoff_db '%s': %s", Client.host,
193                    postrec_dir, strerror(errno));
194             return;
195         }
196     }
197     if (!S_ISDIR(st.st_mode)) {
198         syslog(L_ERROR, "%s backoff_db '%s' is not a directory", Client.host,
199                postrec_dir);
200         return;
201     }
202 
203     BACKOFFenabled = true;
204 
205     return;
206 }
207 
208 /*
209 **  PostRecs are stored in individual files.  I didn't have a better
210 **  way offhand, don't want to touch DBZ, and the number of posters is
211 **  small compared to the number of readers.  This is the filename
212 **  corresponding to an IP number.
213 */
214 char *
PostRecFilename(char * ip,char * user)215 PostRecFilename(char *ip, char *user)
216 {
217     static char buff[SPOOLNAMEBUFF];
218     char dirbuff[SMBUF + 2 + 3 * 3];
219     struct in_addr inaddr;
220     unsigned long int addr;
221     unsigned char quads[4];
222     unsigned int i;
223 
224     if (PERMaccessconf->backoff_auth) {
225         snprintf(buff, sizeof(buff), "%s/%s", postrec_dir, user);
226         return (buff);
227     }
228 
229     if (inet_aton(ip, &inaddr) < 1) {
230         /* If inet_aton() fails, we'll assume it's an IPv6 address.  We'll
231          * also assume for now that we're dealing with a limited number of
232          * IPv6 clients so we'll place their files all in the same
233          * directory for simplicity.  Someday we'll need to change this to
234          * something more scalable such as DBZ when IPv6 clients become
235          * more popular. */
236         snprintf(buff, sizeof(buff), "%s/%s", postrec_dir, ip);
237         return (buff);
238     }
239     /* If it's an IPv4 address just fall through. */
240 
241     addr = ntohl(inaddr.s_addr);
242     for (i = 0; i < 4; i++)
243         quads[i] = (unsigned char) (0xff & (addr >> (i * 8)));
244 
245     snprintf(dirbuff, sizeof(dirbuff), "%s/%03u%03u/%03u", postrec_dir,
246              quads[3], quads[2], quads[1]);
247     if (!MakeDirectory(dirbuff, true)) {
248         syslog(L_ERROR, "%s Unable to create postrec directories '%s': %s",
249                Client.host, dirbuff, strerror(errno));
250         return NULL;
251     }
252     snprintf(buff, sizeof(buff), "%s/%03u", dirbuff, quads[0]);
253     return (buff);
254 }
255 
256 /*
257 **  Lock the post rec file.  Return 1 on lock, 0 on error.
258 */
259 int
LockPostRec(char * path)260 LockPostRec(char *path)
261 {
262     char lockname[SPOOLNAMEBUFF];
263     char temp[SPOOLNAMEBUFF];
264     int statfailed = 0;
265 
266     snprintf(lockname, sizeof(lockname), "%s.lock", path);
267 
268     for (;; sleep(5)) {
269         int fd;
270         struct stat st;
271         time_t now;
272 
273         fd = open(lockname, O_WRONLY | O_EXCL | O_CREAT, 0600);
274         if (fd >= 0) {
275             /* We got the lock! */
276             snprintf(temp, sizeof(temp), "pid:%lu\n",
277                      (unsigned long) getpid());
278             if (write(fd, temp, strlen(temp)) < (ssize_t) strlen(temp)) {
279                 syslog(L_ERROR, "%s cannot write to lock file %s", Client.host,
280                        strerror(errno));
281                 close(fd);
282                 return (0);
283             }
284             close(fd);
285             return (1);
286         }
287 
288         /* No lock.  See if the file is there. */
289         if (stat(lockname, &st) < 0) {
290             syslog(L_ERROR, "%s cannot stat lock file %s", Client.host,
291                    strerror(errno));
292             if (statfailed++ > 5)
293                 return (0);
294             continue;
295         }
296 
297         /* If lockfile is older than the value of
298          * PERMaccessconf->backoff_postslow, remove it. */
299         statfailed = 0;
300         time(&now);
301         if (now < (time_t)(st.st_ctime + PERMaccessconf->backoff_postslow))
302             continue;
303         syslog(L_ERROR, "%s removing stale lock file %s", Client.host,
304                lockname);
305         unlink(lockname);
306     }
307 }
308 
309 void
UnlockPostRec(char * path)310 UnlockPostRec(char *path)
311 {
312     char lockname[SPOOLNAMEBUFF];
313 
314     snprintf(lockname, sizeof(lockname), "%s.lock", path);
315     if (unlink(lockname) < 0) {
316         syslog(L_ERROR, "%s can't unlink lock file: %s", Client.host,
317                strerror(errno));
318     }
319     return;
320 }
321 
322 /*
323 ** Get the stored postrecord for that IP.
324 */
325 static int
GetPostRecord(char * path,long * lastpost,long * lastsleep,long * lastn)326 GetPostRecord(char *path, long *lastpost, long *lastsleep, long *lastn)
327 {
328     static char buff[SMBUF];
329     FILE *fp;
330     char *s;
331 
332     fp = fopen(path, "r");
333     if (fp == NULL) {
334         if (errno == ENOENT) {
335             return 1;
336         }
337         syslog(L_ERROR, "%s Error opening '%s': %s", Client.host, path,
338                strerror(errno));
339         return 0;
340     }
341 
342     if (fgets(buff, SMBUF, fp) == NULL) {
343         syslog(L_ERROR, "%s Error reading '%s': %s", Client.host, path,
344                strerror(errno));
345         fclose(fp);
346         return 0;
347     }
348     *lastpost = atol(buff);
349 
350     if ((s = strchr(buff, ',')) == NULL) {
351         syslog(L_ERROR, "%s bad data in postrec file: '%s'", Client.host,
352                buff);
353         fclose(fp);
354         return 0;
355     }
356     s++;
357     *lastsleep = atol(s);
358 
359     if ((s = strchr(s, ',')) == NULL) {
360         syslog(L_ERROR, "%s bad data in postrec file: '%s'", Client.host,
361                buff);
362         fclose(fp);
363         return 0;
364     }
365     s++;
366     *lastn = atol(s);
367 
368     fclose(fp);
369     return 1;
370 }
371 
372 /*
373 ** Store the postrecord for that IP.
374 */
375 static int
StorePostRecord(char * path,time_t lastpost,long lastsleep,long lastn)376 StorePostRecord(char *path, time_t lastpost, long lastsleep, long lastn)
377 {
378     FILE *fp;
379 
380     fp = fopen(path, "w");
381     if (fp == NULL) {
382         syslog(L_ERROR, "%s Error opening '%s': %s", Client.host, path,
383                strerror(errno));
384         return 0;
385     }
386 
387     fprintf(fp, "%ld,%ld,%ld\n", (long) lastpost, lastsleep, lastn);
388     fclose(fp);
389     return 1;
390 }
391 
392 /*
393 ** Return the proper sleeptime.  Return false on error.
394 */
395 int
RateLimit(long * sleeptime,char * path)396 RateLimit(long *sleeptime, char *path)
397 {
398     time_t now;
399     long prevpost, prevsleep, prevn, n;
400 
401     now = time(NULL);
402     prevpost = 0L;
403     prevsleep = 0L;
404     prevn = 0L;
405     n = 0L;
406     if (!GetPostRecord(path, &prevpost, &prevsleep, &prevn)) {
407         syslog(L_ERROR, "%s can't get post record: %s", Client.host,
408                strerror(errno));
409         return 0;
410     }
411     /* Just because yer paranoid doesn't mean they ain't out ta get ya.
412      * This is called paranoid clipping. */
413     if (prevn < 0L)
414         prevn = 0L;
415     if (prevsleep < 0L)
416         prevsleep = 0L;
417     if ((unsigned long) prevsleep > PERMaccessconf->backoff_postfast)
418         prevsleep = PERMaccessconf->backoff_postfast;
419 
420     /* Compute the new sleep time. */
421     *sleeptime = 0L;
422     if (prevpost <= 0L) {
423         prevpost = 0L;
424         prevn = 1L;
425     } else {
426         n = now - prevpost;
427         if (n < 0L) {
428             syslog(L_NOTICE, "%s previous post was in the future (%ld sec)",
429                    Client.host, n);
430             n = 0L;
431         }
432         if ((unsigned long) n < PERMaccessconf->backoff_postfast) {
433             if ((unsigned long) prevn >= PERMaccessconf->backoff_trigger) {
434                 *sleeptime = 1 + (prevsleep * PERMaccessconf->backoff_k);
435             }
436         } else if ((unsigned long) n < PERMaccessconf->backoff_postslow) {
437             if ((unsigned long) prevn >= PERMaccessconf->backoff_trigger) {
438                 *sleeptime = prevsleep;
439             }
440         } else {
441             prevn = 0L;
442         }
443         prevn++;
444     }
445 
446     *sleeptime = ((*sleeptime) > (long) PERMaccessconf->backoff_postfast)
447                      ? (long) PERMaccessconf->backoff_postfast
448                      : (*sleeptime);
449     /* This ought to trap this bogon. */
450     if ((*sleeptime) < 0L) {
451         syslog(L_ERROR,
452                "%s Negative sleeptime detected: %ld, prevsleep: %ld, N: %ld",
453                Client.host, *sleeptime, prevsleep, n);
454         *sleeptime = 0L;
455     }
456 
457     /* Store the postrecord. */
458     if (!StorePostRecord(path, now, *sleeptime, prevn)) {
459         syslog(L_ERROR, "%s can't store post record: %s", Client.host,
460                strerror(errno));
461         return 0;
462     }
463 
464     return 1;
465 }
466 
467 #if defined(HAVE_SASL) || defined(HAVE_ZLIB)
468 /*
469 **  Check if the argument has a valid syntax.
470 **
471 **  Currently used for both SASL mechanisms (RFC 4643) and compression
472 **  algorithms.
473 **
474 **    algorithm = 1*20alg-char
475 **    alg-char = UPPER / DIGIT / "-" / "_"
476 */
477 bool
IsValidAlgorithm(const char * string)478 IsValidAlgorithm(const char *string)
479 {
480     size_t len = 0;
481     const unsigned char *p;
482 
483     /* Not NULL. */
484     if (string == NULL) {
485         return false;
486     }
487 
488     p = (const unsigned char *) string;
489 
490     for (; *p != '\0'; p++) {
491         len++;
492 
493         if (!isupper((unsigned char) *p) && !isdigit((unsigned char) *p)
494             && *p != '-' && *p != '_') {
495             return false;
496         }
497     }
498 
499     if (len > 0 && len < 21) {
500         return true;
501     } else {
502         return false;
503     }
504 }
505 #endif /* HAVE_SASL || HAVE_ZLIB */
506 
507 #if defined(HAVE_ZLIB)
508 /*
509 **  The COMPRESS command.  RFC 8054.
510 */
511 void
CMDcompress(int ac,char * av[])512 CMDcompress(int ac, char *av[])
513 {
514     bool result;
515 
516     /* Check the argument. */
517     if (ac > 1) {
518         if (!IsValidAlgorithm(av[1])) {
519             Reply("%d Syntax error in compression algorithm name\r\n",
520                   NNTP_ERR_SYNTAX);
521             return;
522         }
523         if (strcasecmp(av[1], "DEFLATE") != 0) {
524             Reply("%d Only the DEFLATE compression algorithm is supported\r\n",
525                   NNTP_ERR_UNAVAILABLE);
526             return;
527         }
528     }
529 
530     if (compression_layer_on) {
531         Reply("%d Already using a compression layer\r\n", NNTP_ERR_ACCESS);
532         return;
533     }
534 
535     result = zlib_init();
536 
537     if (!result) {
538         Reply("%d Impossible to activate compression\r\n", NNTP_FAIL_ACTION);
539         return;
540     }
541 
542     Reply("%d Compression now active; enjoy the speed!\r\n", NNTP_OK_COMPRESS);
543 
544     /* Flush any pending output, before enabling compression. */
545     fflush(stdout);
546 
547     compression_layer_on = true;
548 }
549 #endif /* HAVE_ZLIB */
550 
551 #if defined(HAVE_OPENSSL)
552 /*
553 **  The STARTTLS command.  RFC 4642.
554 */
555 void
CMDstarttls(int ac UNUSED,char * av[]UNUSED)556 CMDstarttls(int ac UNUSED, char *av[] UNUSED)
557 {
558     int result;
559     bool boolval;
560 
561     if (encryption_layer_on) {
562         Reply("%d Already using a security layer\r\n", NNTP_ERR_ACCESS);
563         return;
564     }
565 
566 #    if defined(HAVE_ZLIB)
567     /* If a compression layer is active, STARTTLS is not possible. */
568     if (compression_layer_on) {
569         Reply("%d Already using a compression layer\r\n", NNTP_ERR_ACCESS);
570         return;
571     }
572 #    endif /* HAVE_ZLIB */
573 
574     /* If the client is already authenticated, STARTTLS is not possible. */
575     if (PERMauthorized && !PERMneedauth && !PERMcanauthenticate) {
576         Reply(
577             "%d Already authenticated without the use of a security layer\r\n",
578             NNTP_ERR_ACCESS);
579         return;
580     }
581 
582     result = tls_init();
583 
584     if (result == -1) {
585         /* No reply because tls_init() has already sent one. */
586         return;
587     }
588 
589     /* Close out any existing article, report group stats.
590      * RFC 4642 requires the reset of any knowledge about the client. */
591     if (GRPcur) {
592         ARTclose();
593         GRPreport();
594         OVctl(OVCACHEFREE, &boolval);
595         free(GRPcur);
596         GRPcur = NULL;
597         if (ARTcount) {
598             syslog(L_NOTICE, "%s exit for STARTTLS articles %ld groups %ld",
599                    Client.host, ARTcount, GRPcount);
600         }
601         GRPcount = 0;
602         PERMgroupmadeinvalid = false;
603     }
604 
605     /* We can now assume a secure connection will be negotiated because
606      * nnrpd will exit if STARTTLS fails.
607      * Check the permissions the client will have after having successfully
608      * negotiated a TLS layer.  (There may be auth blocks requiring the
609      * negotiation of a security layer in readers.conf that match the
610      * connection.)
611      * In case the client would no longer have access to the server, or an
612      * authentication error happens, the connection aborts after a fatal 400
613      * response code sent by PERMgetpermissions(). */
614     encryption_layer_on = true;
615     PERMgetaccess(false);
616     PERMgetpermissions();
617 
618     Reply("%d Begin TLS negotiation now\r\n", NNTP_CONT_STARTTLS);
619     fflush(stdout);
620 
621     /* Must flush our buffers before starting TLS. */
622 
623     result = tls_start_servertls(0,  /* Read. */
624                                  1); /* Write. */
625     if (result == -1) {
626         /* No reply because we have already sent NNTP_CONT_STARTTLS.
627          * We close the connection. */
628         ExitWithStats(1, false);
629     }
630 
631 #    if defined(HAVE_SASL)
632     /* Tell SASL about the negotiated layer. */
633     result = sasl_setprop(sasl_conn, SASL_SSF_EXTERNAL,
634                           (sasl_ssf_t *) &tls_cipher_usebits);
635     if (result != SASL_OK) {
636         syslog(L_NOTICE, "sasl_setprop() failed: CMDstarttls()");
637     }
638 
639     result = sasl_setprop(sasl_conn, SASL_AUTH_EXTERNAL, tls_peer_CN);
640     if (result != SASL_OK) {
641         syslog(L_NOTICE, "sasl_setprop() failed: CMDstarttls()");
642     }
643 #    endif /* HAVE_SASL */
644 
645 #    if defined(HAVE_ZLIB) && OPENSSL_VERSION_NUMBER >= 0x00090800fL
646     /* Check whether a compression layer has just been added.
647      * SSL_get_current_compression() is defined in OpenSSL versions >= 0.9.8
648      * final release, as well as LibreSSL. */
649     tls_compression_on = (SSL_get_current_compression(tls_conn) != NULL);
650     compression_layer_on = tls_compression_on;
651 #    endif /* HAVE_ZLIB && OPENSSL >= v0.9.8 */
652 
653     /* Reset our read buffer so as to prevent plaintext command injection. */
654     line_reset(&NNTPline);
655 }
656 #endif /* HAVE_OPENSSL */
657