Ticket #30: misc.c

File misc.c, 18.1 KB (added by eagle, 12 years ago)

Replacement misc.c with new Argify()

Line 
1/*  $Id: misc.c,v 1.52 2001/07/11 07:40:46 alexk Exp $
2**
3**  Miscellaneous support routines.
4*/
5
6#include "config.h"
7#include "clibrary.h"
8#include <netinet/in.h>
9
10/* Needed on AIX 4.1 to get fd_set and friends. */
11#ifdef HAVE_SYS_SELECT_H
12# include <sys/select.h>
13#endif
14
15#include "nnrpd.h"
16
17#ifdef HAVE_SSL
18# include <openssl/ssl.h>
19# include <openssl/err.h>
20# include <openssl/bio.h>
21# include <openssl/pem.h>
22# include "tls.h"
23# include "sasl_config.h"
24#endif
25
26static bool             setup = FALSE;
27static FILE             *hfp = NULL;
28static ino_t            ino = 0;
29
30#ifdef HAVE_SSL
31extern SSL *tls_conn;
32extern int nnrpd_starttls_done;
33#endif
34
35
36
37/*
38**  Parse a string into a NULL-terminated array of words; return number
39**  of words.  If argvp isn't NULL, it and what it points to will be
40**  DISPOSE'd.
41*/
42int
43Argify(line, argvp)
44    char                *line;
45    char                ***argvp;
46{
47    return nArgify(line,argvp,-1);
48}
49
50
51/*
52**  Parse a string into a NULL-terminated array of at most n words;
53**  return number of words.  If there are more than n words, stop
54**  processing at the beginning of the (n+1)th word, store everything
55**  from the beginning of word n+1 in argv[n] and return n+1.  If n is
56**  negative, parses all words.  If argvp isn't NULL, it and what it
57**  points to will be DISPOSE'd.
58*/
59int
60nArgify(line, argvp, n)
61    char                *line;
62    char                ***argvp;
63    int                 n;
64{
65    register char       *p;
66    register int        i;
67
68    if (*argvp != NULL) {
69        DISPOSE(*argvp[0]);
70        DISPOSE(*argvp);
71    }
72
73    /*  Copy the line, which we will split up. */
74    while (ISWHITE(*line))
75        line++;
76    i = strlen(line);
77    p = NEW(char, i + 1);
78    (void)strcpy(p, line);
79
80    *argvp = NEW(char*, i + 2);
81
82    return reArgify(p, *argvp, n);
83}
84
85
86/*
87**  Destructively parse a string into a NULL-terminated array of at most
88**  n words; return number of words.  Behavior on negative n and strings
89**  of more than n words matches that of nArgify (see above).  Caller
90**  must supply an array of sufficient size (such as created by
91**  nArgify). 
92**
93**  Note that the sequence
94**      ac = nArgify(line, &argv, n1);
95**      ac--;
96**      ac += reArgify(argv[ac], &argv[ac], n2);
97**  is equivalent to
98**      ac = nArgify(line, &argv, n1 + n2);
99*/
100int
101reArgify(p, argv, n)
102    register char       *p;
103    char                **argv;
104    int                 n;
105{
106    char                **save=argv;
107
108
109    /* Should never happen unless caller modifies argv between calls */
110    while (ISWHITE(*p))
111        p++;
112
113    for ( ; *p; ) {
114        if (n == 0) {
115            *argv++ = p;
116            break;
117        }
118        /* Decrement limit, mark start of this word, find its end. */
119        for (n--, *argv++ = p; *p && !ISWHITE(*p); )
120            p++;
121        if (*p == '\0')
122            break;
123
124        /* Nip off word, skip whitespace. */
125        for (*p++ = '\0'; ISWHITE(*p); )
126            p++;
127    }
128    *argv = NULL;
129    return argv - save;
130}
131
132
133/*
134**  Take a vector which Argify made and glue it back together with
135**  spaces between each element.  Returns a pointer to dynamic space.
136*/
137char *
138Glom(av)
139    char                **av;
140{
141    register char       **v;
142    register char       *p;
143    register int        i;
144    char                *save;
145
146    /* Get space. */
147    for (i = 0, v = av; *v; v++)
148        i += strlen(*v) + 1;
149
150    for (save = p = NEW(char, i + 1), v = av; *v; v++) {
151        if (p > save)
152            *p++ = ' ';
153        p += strlen(strcpy(p, *v));
154    }
155
156    return save;
157}
158
159
160/*
161**  Match a list of newsgroup specifiers against a list of newsgroups.
162**  func is called to see if there is a match.
163*/
164bool PERMmatch(char **Pats, char **list)
165{
166    int                 i;
167    char                *p;
168    int                 match = FALSE;
169
170    if (Pats == NULL || Pats[0] == NULL)
171        return TRUE;
172
173    for ( ; *list; list++) {
174        for (i = 0; (p = Pats[i]) != NULL; i++) {
175            if (p[0] == '!') {
176                if (wildmat(*list, ++p))
177                    match = FALSE;
178            }
179            else if (wildmat(*list, p))
180                match = TRUE;
181        }
182        if (match)
183            /* If we can read it in one group, we can read it, period. */
184            return TRUE;
185    }
186
187    return FALSE;
188}
189
190
191/*
192**  Check to see if user is allowed to see this article by matching
193**  Newsgroups line.
194*/
195bool
196PERMartok()
197{
198    static char         **grplist;
199    char                *p, **grp;
200
201    if (!PERMspecified)
202        return FALSE;
203
204    if ((p = GetHeader("Xref", FALSE)) == NULL) {
205        /* in case article does not include Xref */
206        if ((p = GetHeader("Newsgroups", FALSE)) == NULL)
207            if (!NGgetlist(&grplist, p))
208                /* No newgroups or null entry. */
209                return TRUE;
210    } else {
211        /* skip path element */
212        if ((p = strchr(p, ' ')) == NULL)
213            return TRUE;
214        for (p++ ; *p == ' ' ; p++);
215        if (*p == '\0')
216            return TRUE;
217        if (!NGgetlist(&grplist, p))
218            /* No newgroups or null entry. */
219            return TRUE;
220        /* chop ':' and article number */
221        for (grp = grplist ; *grp != NULL ; grp++) {
222            if ((p = strchr(*grp, ':')) == NULL)
223                return TRUE;
224            *p = '\0';
225        }
226    }
227
228#ifdef DO_PYTHON
229    if (innconf->nnrppythonauth) {
230        char    *reply;
231
232        /* Authorize user at a Python authorization module */
233        if (PY_authorize(ClientHost, ClientIp, ServerHost, PERMuser, p, FALSE, &reply) < 0) {
234            syslog(L_NOTICE, "PY_authorize(): authorization skipped due to no Python authorization method defined.");
235        } else {
236            if (reply != NULL) {
237                syslog(L_TRACE, "PY_authorize() returned a refuse string for user %s at %s who wants to read %s: %s", PERMuser, ClientHost, p, reply);
238                return TRUE;
239            }
240        }
241    }
242#endif /* DO_PYTHON */
243
244    return PERMmatch(PERMreadlist, grplist);
245}
246
247
248/*
249**  Parse a newsgroups line, return TRUE if there were any.
250*/
251bool
252NGgetlist(argvp, list)
253    char                ***argvp;
254    char                *list;
255{
256    register char       *p;
257
258    for (p = list; *p; p++)
259        if (*p == ',')
260            *p = ' ';
261
262    return Argify(list, argvp) != 0;
263}
264
265
266/*
267**  Take an NNTP distribution list <d1,d2,...> and turn it into an array.
268*/
269bool
270ParseDistlist(argvp, list)
271    char                ***argvp;
272    char                *list;
273{
274    static char         **argv;
275    register char       *p;
276
277    if (list[0] != '<' || (p = strchr(&list[1], '>')) == NULL)
278        return FALSE;
279    *p = '\0';
280
281    for (p = list + 1; *p; p++)
282        if (*p == ',')
283            *p = ' ';
284    (void)Argify(list + 1, &argv);
285    *argvp = argv;
286    return TRUE;
287}
288
289
290/*
291**  Read a line of input, with timeout.
292**
293**  'size' is both the size of the input buffer and the max line
294**  size as defined by the RFCs.  The max line size includes
295**  the trailing CR LF (which we strip off here), but not the '\0'.
296**  For NNTP commands, this limit is 512 (or 510 without the CR LF),
297**  and for POST data (article) lines it's 1000 (or 998 without the CR LF).
298*/
299READTYPE READline(char *start, int  size, int timeout)
300{
301    static int          count;
302    static char         buffer[BUFSIZ];
303    static char         *bp;
304    register char       *p;
305    register char       *end;
306    struct timeval      t, stv, etv;
307    fd_set              rmask;
308    int                 i;
309    char                c;
310    bool                toolong;
311    TIMEINFO            Start, End;
312
313    toolong = FALSE;
314
315    for (p = start, end = &start[size - 1]; ; ) {
316        if (count == 0) {
317            /* Fill the buffer. */
318    Again:
319            FD_ZERO(&rmask);
320            FD_SET(STDIN_FILENO, &rmask);
321            t.tv_sec = timeout;
322            t.tv_usec = 0;
323            gettimeofday(&stv, NULL);
324            i = select(STDIN_FILENO + 1, &rmask, NULL, NULL, &t);
325            gettimeofday(&etv, NULL);
326            Start.time = stv.tv_sec;
327            Start.usec = stv.tv_usec;
328            End.time = etv.tv_sec;
329            End.usec = etv.tv_usec;
330            IDLEtime += TIMEINFOasDOUBLE(End) - TIMEINFOasDOUBLE(Start);
331            if (i < 0) {
332                if (errno == EINTR)
333                    goto Again;
334                syslog(L_ERROR, "%s cant select %m", ClientHost);
335                return RTtimeout;
336            }
337            if (i == 0 || !FD_ISSET(STDIN_FILENO, &rmask))
338                return RTtimeout;
339#ifdef HAVE_SSL
340            if (tls_conn)
341              count = SSL_read(tls_conn, buffer, sizeof buffer);
342            else
343              count = read(STDIN_FILENO, buffer, sizeof buffer);
344#else
345            count = read(STDIN_FILENO, buffer, sizeof buffer);
346#endif
347            if (count < 0) {
348                syslog(L_TRACE, "%s cant read %m", ClientHost);
349                return RTtimeout;
350            }
351            if (count == 0) {
352                *p = '\0';
353                return RTeof;
354            }
355            bp = buffer;
356        }
357
358        /* Process next character. */
359        count--;
360        c = *bp++;
361        if (c == '\n') {
362            /* If last two characters are \r\n, kill the \r as well as the \n. */
363            if (!toolong && p > start && p[-1] == '\r')
364                p--;
365            break;
366        }
367        if (p < end)
368            *p++ = c;
369        else
370            toolong = TRUE;
371    }
372
373    *p = '\0';
374    return toolong ? RTlong : RTok;
375}
376
377/*********************************************************************
378 * POSTING RATE LIMITS - The following code implements posting rate
379 * limits. News clients are indexed by IP number (or PERMuser, see
380 * config file). After a relatively configurable number of posts, the nnrpd
381 * process will sleep for a period of time before posting anything.
382 *
383 * Each time that IP number posts a message, the time of
384 * posting and the previous sleep time is stored. The new sleep time
385 * is computed based on these values.
386 *
387 * To compute the new sleep time, the previous sleep time is, for most
388 * cases multiplied by a factor (backoff_k).
389 *
390 * See inn.conf(5) for how this code works
391 *
392 *********************************************************************/
393
394/* Defaults are pass through, i.e. not enabled
395 * NEW for INN 1.8 - Use the inn.conf file to specify the following:
396 *
397 * backoff_k: <integer>
398 * backoff_postfast: <integer>
399 * backoff_postslow: <integer>
400 * backoff_trigger: <integer>
401 * backoff_db: <path>
402 * backoff_auth: <on|off>
403 *
404 * You may also specify posting backoffs on a per user basis. To do this
405 * turn on "backoff_auth"
406 *
407 * Now these are runtime constants. <grin>
408 */
409static char postrec_dir[SMBUF];   /* Where is the post record directory? */
410
411void
412InitBackoffConstants()
413{
414  struct stat st;
415
416  /* Default is not to enable this code */
417  BACKOFFenabled = FALSE;
418 
419  /* Read the runtime config file to get parameters */
420
421  if ((PERMaccessconf->backoff_db == NULL) ||
422    !(PERMaccessconf->backoff_k >= 0L && PERMaccessconf->backoff_postfast >= 0L && PERMaccessconf->backoff_postslow >= 1L))
423    return;
424
425  /* Need this database for backing off */
426  (void)strncpy(postrec_dir,PERMaccessconf->backoff_db,SMBUF);
427  if (stat(postrec_dir, &st) < 0) {
428    if (ENOENT == errno) {
429      if (!MakeDirectory(postrec_dir, true)) {
430        syslog(L_ERROR, "%s cannot create backoff_db '%s': %s",ClientHost,postrec_dir,strerror(errno));
431        return;
432      }
433    } else {
434      syslog(L_ERROR, "%s cannot stat backoff_db '%s': %s",ClientHost,postrec_dir,strerror(errno));
435      return;
436    }
437  }
438  if (!S_ISDIR(st.st_mode)) {
439    syslog(L_ERROR, "%s backoff_db '%s' is not a directory",ClientHost,postrec_dir);
440    return;
441  }
442
443  BACKOFFenabled = TRUE;
444
445  return;
446}
447
448/*
449 * PostRecs are stored in individual files. I didn't have a better
450 * way offhand, don't want to touch DBZ, and the number of posters is
451 * small compared to the number of readers. This is the filename corresponding
452 * to an IP number.
453 */
454char
455*PostRecFilename(ip,user) 
456     unsigned long                 ip;
457     char                         *user;
458{
459     static char                   buff[SPOOLNAMEBUFF];
460     char                          dirbuff[SPOOLNAMEBUFF];
461     unsigned char addr[4];
462     unsigned int i;
463
464     if (PERMaccessconf->backoff_auth) {
465       sprintf(buff,"%s/%s",postrec_dir,user);
466       return(buff);
467     }
468
469     for (i=0; i<4; i++) {
470       addr[i] = (unsigned char) (0x000000ff & (ip>>(i*8)));
471     }
472
473     sprintf(dirbuff,"%s/%03d%03d/%03d",postrec_dir,addr[3],addr[2],addr[1]);
474     if (!MakeDirectory(dirbuff,TRUE)) {
475       syslog(L_ERROR,"%s Unable to create postrec directories '%s': %s",
476               ClientHost,dirbuff,strerror(errno));
477       return NULL;
478     }
479     sprintf(buff,"%s/%03d",dirbuff,addr[0]);
480     return(buff);
481}
482
483/*
484 * Lock the post rec file. Return 1 on lock, 0 on error
485 */
486int
487LockPostRec(path)
488     char              *path;
489{
490  char lockname[SPOOLNAMEBUFF]; 
491  char temp[SPOOLNAMEBUFF];
492  int statfailed = 0;
493 
494  sprintf(lockname, "%s.lock", path);
495
496  for (;; sleep(5)) {
497    int fd;
498    struct stat st;
499    time_t now;
500 
501    fd = open(lockname, O_WRONLY|O_EXCL|O_CREAT, 0600);
502    if (fd >= 0) {
503      /* We got the lock! */
504      sprintf(temp, "pid:%ld\n", (unsigned long) getpid());
505      write(fd, temp, strlen(temp));
506      close(fd);
507      return(1);
508    }
509
510    /* No lock. See if the file is there. */
511    if (stat(lockname, &st) < 0) {
512      syslog(L_ERROR, "%s cannot stat lock file %s", ClientHost, strerror(errno));
513      if (statfailed++ > 5) return(0);
514      continue;
515    }
516
517    /* If lockfile is older than the value of PERMaccessconf->backoff_postslow, remove it
518     */
519    statfailed = 0;
520    time(&now);
521    if (now < st.st_ctime + PERMaccessconf->backoff_postslow) continue;
522    syslog(L_ERROR, "%s removing stale lock file %s", ClientHost, lockname);
523    unlink(lockname);
524  }
525}
526
527void
528UnlockPostRec(path)
529     char              *path;
530{
531  char lockname[SPOOLNAMEBUFF]; 
532
533  sprintf(lockname, "%s.lock", path);
534  if (unlink(lockname) < 0) {
535    syslog(L_ERROR, "%s can't unlink lock file: %s", ClientHost,strerror(errno)) ;
536  }
537  return;
538}
539
540/*
541 * Get the stored postrecord for that IP
542 */
543static int
544GetPostRecord(path, lastpost, lastsleep, lastn)
545     char                         *path;
546     long                *lastpost;
547     long                *lastsleep;
548     long                *lastn;
549{
550     static char                   buff[SMBUF];
551     FILE                         *fp;
552     char                         *s;
553
554     fp = fopen(path,"r");
555     if (fp == NULL) { 
556       if (errno == ENOENT) {
557         return 1;
558       }
559       syslog(L_ERROR, "%s Error opening '%s': %s",
560              ClientHost, path, strerror(errno));
561       return 0;
562     }
563
564     if (fgets(buff,SMBUF,fp) == NULL) {
565       syslog(L_ERROR, "%s Error reading '%s': %s",
566              ClientHost, path, strerror(errno));
567       return 0;
568     }
569     *lastpost = atol(buff);
570
571     if ((s = strchr(buff,',')) == NULL) {
572       syslog(L_ERROR, "%s bad data in postrec file: '%s'",
573              ClientHost, buff);
574       return 0;
575     }
576     s++; *lastsleep = atol(s);
577
578     if ((s = strchr(s,',')) == NULL) {
579       syslog(L_ERROR, "%s bad data in postrec file: '%s'",
580              ClientHost, buff);
581       return 0;
582     }
583     s++; *lastn = atol(s);
584
585     (void)fclose(fp);
586     return 1;
587}
588
589/*
590 * Store the postrecord for that IP
591 */
592static int
593StorePostRecord(path, lastpost, lastsleep, lastn)
594     char                         *path;
595     time_t                       lastpost;
596     long                         lastsleep;
597     long                         lastn;
598{
599     FILE                         *fp;
600
601     fp = fopen(path,"w");
602     if (fp == NULL)                   {
603       syslog(L_ERROR, "%s Error opening '%s': %s",
604              ClientHost, path, strerror(errno));
605       return 0;
606     }
607
608     fprintf(fp,"%ld,%ld,%ld\n",lastpost,lastsleep,lastn);
609     (void)fclose(fp);
610     return 1;
611}
612
613/*
614 * Return the proper sleeptime. Return false on error.
615 */
616int
617RateLimit(sleeptime,path) 
618     long                         *sleeptime;
619     char                         *path;
620{
621     TIMEINFO                      Now;
622     long                          prevpost,prevsleep,prevn,n;
623
624     if (GetTimeInfo(&Now) < 0) 
625       return 0;
626     
627     prevpost = 0L; prevsleep = 0L; prevn = 0L;
628     if (!GetPostRecord(path,&prevpost,&prevsleep,&prevn)) {
629       syslog(L_ERROR, "%s can't get post record: %s",
630              ClientHost, strerror(errno));
631       return 0;
632     }
633     /*
634      * Just because yer paranoid doesn't mean they ain't out ta get ya
635      * This is called paranoid clipping
636      */
637     if (prevn < 0L) prevn = 0L;
638     if (prevsleep < 0L)  prevsleep = 0L;
639     if (prevsleep > PERMaccessconf->backoff_postfast)  prevsleep = PERMaccessconf->backoff_postfast;
640     
641      /*
642       * Compute the new sleep time
643       */
644     *sleeptime = 0L; 
645     if (prevpost <= 0L) {
646       prevpost = 0L;
647       prevn = 1L;
648     } else {
649       n = Now.time - prevpost;
650       if (n < 0L) {
651         syslog(L_NOTICE,"%s previous post was in the future (%ld sec)",
652                ClientHost,n);
653         n = 0L;
654       }
655       if (n < PERMaccessconf->backoff_postfast) {
656         if (prevn >= PERMaccessconf->backoff_trigger) {
657           *sleeptime = 1 + (prevsleep * PERMaccessconf->backoff_k);
658         } 
659       } else if (n < PERMaccessconf->backoff_postslow) {
660         if (prevn >= PERMaccessconf->backoff_trigger) {
661           *sleeptime = prevsleep;
662         }
663       } else {
664         prevn = 0L;
665       } 
666       prevn++;
667     }
668
669     *sleeptime = ((*sleeptime) > PERMaccessconf->backoff_postfast) ? PERMaccessconf->backoff_postfast : (*sleeptime);
670     /* This ought to trap this bogon */
671     if ((*sleeptime) < 0L) {
672        syslog(L_ERROR,"%s Negative sleeptime detected: %ld, prevsleep: %ld, N: %ld",ClientHost,*sleeptime,prevsleep,n);
673        *sleeptime = 0L;
674     }
675 
676     /* Store the postrecord */
677     if (!StorePostRecord(path,Now.time,*sleeptime,prevn)) {
678       syslog(L_ERROR, "%s can't store post record: %s", ClientHost, strerror(errno));
679       return 0;
680     }
681
682     return 1;
683}
684
685#ifdef HAVE_SSL
686/*
687**  The "STARTTLS" command.  RFC2595.
688*/
689/* ARGSUSED0 */
690
691void
692CMDstarttls(ac, av)
693    int         ac;
694    char        *av[];
695{
696  SSL_CTX *ctx;
697  int result;
698
699  sasl_config_read();
700
701  if (nnrpd_starttls_done == 1)
702    {
703      Reply("%d Already successfully executed STARTTLS\r\n", NNTP_STARTTLS_DONE_VAL);
704      return;
705    }
706
707  result=tls_init_serverengine(5,        /* depth to verify */
708                               1,        /* can client auth? */
709                               0,        /* required client to auth? */
710                               (char *)sasl_config_getstring("tls_ca_file", ""),
711                               (char *)sasl_config_getstring("tls_ca_path", ""),
712                               (char *)sasl_config_getstring("tls_cert_file", ""),
713                               (char *)sasl_config_getstring("tls_key_file", ""));
714
715  if (result == -1) {
716    Reply("%d Error initializing TLS\r\n", NNTP_STARTTLS_BAD_VAL);
717   
718    syslog(L_ERROR, "error initializing TLS: "
719           "[CA_file: %s] [CA_path: %s] [cert_file: %s] [key_file: %s]",
720           (char *) sasl_config_getstring("tls_ca_file", ""),
721           (char *) sasl_config_getstring("tls_ca_path", ""),
722           (char *) sasl_config_getstring("tls_cert_file", ""),
723           (char *) sasl_config_getstring("tls_key_file", ""));
724    return;
725  }
726  Reply("%d Begin TLS negotiation now\r\n", NNTP_STARTTLS_NEXT_VAL);
727  (void)fflush(stdout);
728
729  /* must flush our buffers before starting tls */
730 
731  result=tls_start_servertls(0, /* read */
732                             1); /* write */
733  if (result==-1) {
734    Reply("%d Starttls failed\r\n", NNTP_STARTTLS_BAD_VAL);
735    return;
736  }
737  nnrpd_starttls_done = 1;
738}
739#endif /* HAVE_SSL */