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