chiark / gitweb /
Many options, and a manpage
[innduct.git] / nnrpd / misc.c
1 /*  $Id: misc.c 6535 2003-12-10 09:02:22Z rra $
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 #include "sasl_config.h"
18
19 #ifdef HAVE_SSL
20 extern SSL *tls_conn;
21 extern int nnrpd_starttls_done;
22 #endif 
23
24
25 /*
26 **  Parse a string into a NULL-terminated array of words; return number
27 **  of words.  If argvp isn't NULL, it and what it points to will be freed.
28 */
29 int
30 Argify(line, argvp)
31     char                *line;
32     char                ***argvp;
33 {
34     char        **argv;
35     char        *p;
36
37     if (*argvp != NULL) {
38         free(*argvp[0]);
39         free(*argvp);
40     }
41
42     /*  Copy the line, which we will split up. */
43     while (ISWHITE(*line))
44         line++;
45     p = xstrdup(line);
46
47     /* Allocate worst-case amount of space. */
48     for (*argvp = argv = xmalloc((strlen(p) + 2) * sizeof(char *)); *p; ) {
49         /* Mark start of this word, find its end. */
50         for (*argv++ = p; *p && !ISWHITE(*p); )
51             p++;
52         if (*p == '\0')
53             break;
54
55         /* Nip off word, skip whitespace. */
56         for (*p++ = '\0'; ISWHITE(*p); )
57             p++;
58     }
59     *argv = NULL;
60     return argv - *argvp;
61 }
62
63
64 /*
65 **  Take a vector which Argify made and glue it back together with
66 **  spaces between each element.  Returns a pointer to dynamic space.
67 */
68 char *
69 Glom(av)
70     char                **av;
71 {
72     char        **v;
73     int i;
74     char                *save;
75
76     /* Get space. */
77     for (i = 0, v = av; *v; v++)
78         i += strlen(*v) + 1;
79     i++;
80
81     save = xmalloc(i);
82     save[0] = '\0';
83     for (v = av; *v; v++) {
84         if (v > av)
85             strlcat(save, " ", i);
86         strlcat(save, *v, i);
87     }
88
89     return save;
90 }
91
92
93 /*
94 **  Match a list of newsgroup specifiers against a list of newsgroups.
95 **  func is called to see if there is a match.
96 */
97 bool PERMmatch(char **Pats, char **list)
98 {
99     int                 i;
100     char                *p;
101     int                 match = false;
102
103     if (Pats == NULL || Pats[0] == NULL)
104         return true;
105
106     for ( ; *list; list++) {
107         for (i = 0; (p = Pats[i]) != NULL; i++) {
108             if (p[0] == '!') {
109                 if (uwildmat(*list, ++p))
110                     match = false;
111             }
112             else if (uwildmat(*list, p))
113                 match = true;
114         }
115         if (match)
116             /* If we can read it in one group, we can read it, period. */
117             return true;
118     }
119
120     return false;
121 }
122
123
124 /*
125 **  Check to see if user is allowed to see this article by matching
126 **  Newsgroups line.
127 */
128 bool
129 PERMartok(void)
130 {
131     static char         **grplist;
132     char                *p, **grp;
133
134     if (!PERMspecified)
135         return false;
136
137     if ((p = GetHeader("Xref")) == NULL) {
138         /* in case article does not include Xref */
139         if ((p = GetHeader("Newsgroups")) != NULL) {
140             if (!NGgetlist(&grplist, p))
141                 /* No newgroups or null entry. */
142                 return true;
143         } else {
144             return true;
145         }
146     } else {
147         /* skip path element */
148         if ((p = strchr(p, ' ')) == NULL)
149             return true;
150         for (p++ ; *p == ' ' ; p++);
151         if (*p == '\0')
152             return true;
153         if (!NGgetlist(&grplist, p))
154             /* No newgroups or null entry. */
155             return true;
156         /* chop ':' and article number */
157         for (grp = grplist ; *grp != NULL ; grp++) {
158             if ((p = strchr(*grp, ':')) == NULL)
159                 return true;
160             *p = '\0';
161         }
162     }
163
164 #ifdef DO_PYTHON
165     if (PY_use_dynamic) {
166         char    *reply;
167
168         /* Authorize user at a Python authorization module */
169         if (PY_dynamic(PERMuser, p, false, &reply) < 0) {
170             syslog(L_NOTICE, "PY_dynamic(): authorization skipped due to no Python dynamic method defined.");
171         } else {
172             if (reply != NULL) {
173                 syslog(L_TRACE, "PY_dynamic() returned a refuse string for user %s at %s who wants to read %s: %s", PERMuser, ClientHost, p, reply);
174                 free(reply);
175                 return false;
176             }
177             return true;
178         }
179     }
180 #endif /* DO_PYTHON */
181
182     return PERMmatch(PERMreadlist, grplist);
183 }
184
185
186 /*
187 **  Parse a newsgroups line, return true if there were any.
188 */
189 bool
190 NGgetlist(argvp, list)
191     char                ***argvp;
192     char                *list;
193 {
194     char        *p;
195
196     for (p = list; *p; p++)
197         if (*p == ',')
198             *p = ' ';
199
200     return Argify(list, argvp) != 0;
201 }
202
203
204 /*********************************************************************
205  * POSTING RATE LIMITS - The following code implements posting rate
206  * limits. News clients are indexed by IP number (or PERMuser, see
207  * config file). After a relatively configurable number of posts, the nnrpd
208  * process will sleep for a period of time before posting anything.
209  * 
210  * Each time that IP number posts a message, the time of
211  * posting and the previous sleep time is stored. The new sleep time
212  * is computed based on these values.
213  *
214  * To compute the new sleep time, the previous sleep time is, for most
215  * cases multiplied by a factor (backoff_k). 
216  *
217  * See inn.conf(5) for how this code works
218  *
219  *********************************************************************/
220
221 /* Defaults are pass through, i.e. not enabled 
222  * NEW for INN 1.8 - Use the inn.conf file to specify the following:
223  *
224  * backoff_k: <integer>
225  * backoff_postfast: <integer>
226  * backoff_postslow: <integer>
227  * backoff_trigger: <integer>
228  * backoff_db: <path>
229  * backoff_auth: <on|off> 
230  *
231  * You may also specify posting backoffs on a per user basis. To do this
232  * turn on "backoff_auth"
233  *
234  * Now these are runtime constants. <grin>
235  */
236 static char postrec_dir[SMBUF];   /* Where is the post record directory? */
237
238 void
239 InitBackoffConstants()
240 {
241   struct stat st;
242
243   /* Default is not to enable this code */
244   BACKOFFenabled = false;
245   
246   /* Read the runtime config file to get parameters */
247
248   if ((PERMaccessconf->backoff_db == NULL) ||
249     !(PERMaccessconf->backoff_k >= 0L && PERMaccessconf->backoff_postfast >= 0L && PERMaccessconf->backoff_postslow >= 1L))
250     return;
251
252   /* Need this database for backing off */
253   strlcpy(postrec_dir, PERMaccessconf->backoff_db, sizeof(postrec_dir));
254   if (stat(postrec_dir, &st) < 0) {
255     if (ENOENT == errno) {
256       if (!MakeDirectory(postrec_dir, true)) {
257         syslog(L_ERROR, "%s cannot create backoff_db '%s': %s",ClientHost,postrec_dir,strerror(errno));
258         return;
259       }
260     } else {
261       syslog(L_ERROR, "%s cannot stat backoff_db '%s': %s",ClientHost,postrec_dir,strerror(errno));
262       return;
263     }
264   }
265   if (!S_ISDIR(st.st_mode)) {
266     syslog(L_ERROR, "%s backoff_db '%s' is not a directory",ClientHost,postrec_dir);
267     return;
268   }
269
270   BACKOFFenabled = true;
271
272   return;
273 }
274
275 /*
276  * PostRecs are stored in individual files. I didn't have a better
277  * way offhand, don't want to touch DBZ, and the number of posters is
278  * small compared to the number of readers. This is the filename corresponding
279  * to an IP number.
280  */
281 char
282 *PostRecFilename(ip,user) 
283      char                         *ip;
284      char                         *user;
285 {
286      static char                   buff[SPOOLNAMEBUFF];
287      char                          dirbuff[SPOOLNAMEBUFF];
288      struct in_addr                inaddr;
289      unsigned long int             addr;
290      unsigned char                 quads[4];
291      unsigned int                  i;
292
293      if (PERMaccessconf->backoff_auth) {
294        snprintf(buff, sizeof(buff), "%s/%s", postrec_dir, user);
295        return(buff);
296      }
297
298      if (inet_aton(ip, &inaddr) < 1) {
299        /* If inet_aton() fails, we'll assume it's an IPv6 address.  We'll
300         * also assume for now that we're dealing with a limited number of
301         * IPv6 clients so we'll place their files all in the same 
302         * directory for simplicity.  Someday we'll need to change this to
303         * something more scalable such as DBZ when IPv6 clients become
304         * more popular. */
305        snprintf(buff, sizeof(buff), "%s/%s", postrec_dir, ip);
306        return(buff);
307      }
308      /* If it's an IPv4 address just fall through. */
309
310      addr = ntohl(inaddr.s_addr);
311      for (i=0; i<4; i++)
312        quads[i] = (unsigned char) (0xff & (addr>>(i*8)));
313
314      snprintf(dirbuff, sizeof(dirbuff), "%s/%03d%03d/%03d",
315          postrec_dir, quads[3], quads[2], quads[1]);
316      if (!MakeDirectory(dirbuff,true)) {
317        syslog(L_ERROR, "%s Unable to create postrec directories '%s': %s",
318                ClientHost, dirbuff, strerror(errno));
319        return NULL;
320      }
321      snprintf(buff, sizeof(buff), "%s/%03d", dirbuff, quads[0]);
322      return(buff);
323 }
324
325 /*
326  * Lock the post rec file. Return 1 on lock, 0 on error
327  */
328 int
329 LockPostRec(path)
330      char              *path;
331 {
332   char lockname[SPOOLNAMEBUFF];  
333   char temp[SPOOLNAMEBUFF];
334   int statfailed = 0;
335  
336   snprintf(lockname, sizeof(lockname), "%s.lock", path);
337
338   for (;; sleep(5)) {
339     int fd;
340     struct stat st;
341     time_t now;
342  
343     fd = open(lockname, O_WRONLY|O_EXCL|O_CREAT, 0600);
344     if (fd >= 0) {
345       /* We got the lock! */
346       snprintf(temp, sizeof(temp), "pid:%ld\n", (unsigned long) getpid());
347       write(fd, temp, strlen(temp));
348       close(fd);
349       return(1);
350     }
351
352     /* No lock. See if the file is there. */
353     if (stat(lockname, &st) < 0) {
354       syslog(L_ERROR, "%s cannot stat lock file %s", ClientHost, strerror(errno));
355       if (statfailed++ > 5) return(0);
356       continue;
357     }
358
359     /* If lockfile is older than the value of
360        PERMaccessconf->backoff_postslow, remove it */
361     statfailed = 0;
362     time(&now);
363     if (now < st.st_ctime + PERMaccessconf->backoff_postslow) continue;
364     syslog(L_ERROR, "%s removing stale lock file %s", ClientHost, lockname);
365     unlink(lockname);
366   }
367 }
368
369 void
370 UnlockPostRec(path)
371      char              *path;
372 {
373   char lockname[SPOOLNAMEBUFF];  
374
375   snprintf(lockname, sizeof(lockname), "%s.lock", path);
376   if (unlink(lockname) < 0) {
377     syslog(L_ERROR, "%s can't unlink lock file: %s", ClientHost,strerror(errno)) ;
378   }
379   return;
380 }
381
382 /* 
383  * Get the stored postrecord for that IP 
384  */
385 static int
386 GetPostRecord(char *path, long *lastpost, long *lastsleep, long *lastn)
387 {
388      static char                   buff[SMBUF];
389      FILE                         *fp;
390      char                         *s;
391
392      fp = fopen(path,"r");
393      if (fp == NULL) { 
394        if (errno == ENOENT) {
395          return 1;
396        }
397        syslog(L_ERROR, "%s Error opening '%s': %s",
398               ClientHost, path, strerror(errno));
399        return 0;
400      }
401
402      if (fgets(buff,SMBUF,fp) == NULL) {
403        syslog(L_ERROR, "%s Error reading '%s': %s",
404               ClientHost, path, strerror(errno));
405        return 0;
406      }
407      *lastpost = atol(buff);
408
409      if ((s = strchr(buff,',')) == NULL) {
410        syslog(L_ERROR, "%s bad data in postrec file: '%s'",
411               ClientHost, buff);
412        return 0;
413      }
414      s++; *lastsleep = atol(s);
415
416      if ((s = strchr(s,',')) == NULL) {
417        syslog(L_ERROR, "%s bad data in postrec file: '%s'",
418               ClientHost, buff);
419        return 0;
420      }
421      s++; *lastn = atol(s);
422
423      fclose(fp);
424      return 1;
425 }
426
427 /* 
428  * Store the postrecord for that IP 
429  */
430 static int
431 StorePostRecord(char *path, time_t lastpost, long lastsleep, long lastn)
432 {
433      FILE                         *fp;
434
435      fp = fopen(path,"w");
436      if (fp == NULL)                   {
437        syslog(L_ERROR, "%s Error opening '%s': %s",
438               ClientHost, path, strerror(errno));
439        return 0;
440      }
441
442      fprintf(fp,"%ld,%ld,%ld\n",(long) lastpost,lastsleep,lastn);
443      fclose(fp);
444      return 1;
445 }
446
447 /*
448  * Return the proper sleeptime. Return false on error.
449  */
450 int
451 RateLimit(sleeptime,path) 
452      long                         *sleeptime;
453      char                         *path;
454 {
455      TIMEINFO                      Now;
456      long                          prevpost,prevsleep,prevn,n;
457
458      if (GetTimeInfo(&Now) < 0) 
459        return 0;
460      
461      prevpost = 0L; prevsleep = 0L; prevn = 0L; n = 0L;
462      if (!GetPostRecord(path,&prevpost,&prevsleep,&prevn)) {
463        syslog(L_ERROR, "%s can't get post record: %s",
464               ClientHost, strerror(errno));
465        return 0;
466      }
467      /*
468       * Just because yer paranoid doesn't mean they ain't out ta get ya
469       * This is called paranoid clipping
470       */
471      if (prevn < 0L) prevn = 0L;
472      if (prevsleep < 0L)  prevsleep = 0L;
473      if (prevsleep > PERMaccessconf->backoff_postfast)  prevsleep = PERMaccessconf->backoff_postfast;
474      
475       /*
476        * Compute the new sleep time
477        */
478      *sleeptime = 0L;  
479      if (prevpost <= 0L) {
480        prevpost = 0L;
481        prevn = 1L;
482      } else {
483        n = Now.time - prevpost;
484        if (n < 0L) {
485          syslog(L_NOTICE,"%s previous post was in the future (%ld sec)",
486                 ClientHost,n);
487          n = 0L;
488        }
489        if (n < PERMaccessconf->backoff_postfast) {
490          if (prevn >= PERMaccessconf->backoff_trigger) {
491            *sleeptime = 1 + (prevsleep * PERMaccessconf->backoff_k);
492          } 
493        } else if (n < PERMaccessconf->backoff_postslow) {
494          if (prevn >= PERMaccessconf->backoff_trigger) {
495            *sleeptime = prevsleep;
496          }
497        } else {
498          prevn = 0L;
499        } 
500        prevn++;
501      }
502
503      *sleeptime = ((*sleeptime) > PERMaccessconf->backoff_postfast) ? PERMaccessconf->backoff_postfast : (*sleeptime);
504      /* This ought to trap this bogon */
505      if ((*sleeptime) < 0L) {
506         syslog(L_ERROR,"%s Negative sleeptime detected: %ld, prevsleep: %ld, N: %ld",ClientHost,*sleeptime,prevsleep,n);
507         *sleeptime = 0L;
508      }
509   
510      /* Store the postrecord */
511      if (!StorePostRecord(path,Now.time,*sleeptime,prevn)) {
512        syslog(L_ERROR, "%s can't store post record: %s", ClientHost, strerror(errno));
513        return 0;
514      }
515
516      return 1;
517 }
518
519 #ifdef HAVE_SSL
520 /*
521 **  The "STARTTLS" command.  RFC2595.
522 */
523 /* ARGSUSED0 */
524
525 void
526 CMDstarttls(ac, av)
527     int         ac UNUSED;
528     char        *av[] UNUSED;
529 {
530   int result;
531
532   tls_init();
533   if (nnrpd_starttls_done == 1) {
534       Reply("%d Already successfully executed STARTTLS\r\n",
535             NNTP_STARTTLS_DONE_VAL);
536       return;
537   }
538
539   Reply("%d Begin TLS negotiation now\r\n", NNTP_STARTTLS_NEXT_VAL);
540   fflush(stdout);
541
542   /* must flush our buffers before starting tls */
543   
544   result=tls_start_servertls(0, /* read */
545                              1); /* write */
546   if (result==-1) {
547     /* No reply because we have already sent NNTP_STARTTLS_NEXT_VAL. */
548     return;
549   }
550   nnrpd_starttls_done = 1;
551 }
552 #endif /* HAVE_SSL */