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