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