chiark / gitweb /
run debian/rules patch
[innduct.git] / .pc / u_tls_duplicate_reply / nnrpd / misc.c
diff --git a/.pc/u_tls_duplicate_reply/nnrpd/misc.c b/.pc/u_tls_duplicate_reply/nnrpd/misc.c
new file mode 100644 (file)
index 0000000..fc91471
--- /dev/null
@@ -0,0 +1,552 @@
+/*  $Id: misc.c 6535 2003-12-10 09:02:22Z rra $
+**
+**  Miscellaneous support routines.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+/* Needed on AIX 4.1 to get fd_set and friends. */
+#ifdef HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif
+
+#include "inn/innconf.h"
+#include "nnrpd.h"
+#include "tls.h"
+#include "sasl_config.h"
+
+#ifdef HAVE_SSL
+extern SSL *tls_conn;
+extern int nnrpd_starttls_done;
+#endif 
+
+
+/*
+**  Parse a string into a NULL-terminated array of words; return number
+**  of words.  If argvp isn't NULL, it and what it points to will be freed.
+*/
+int
+Argify(line, argvp)
+    char               *line;
+    char               ***argvp;
+{
+    char       **argv;
+    char       *p;
+
+    if (*argvp != NULL) {
+       free(*argvp[0]);
+       free(*argvp);
+    }
+
+    /*  Copy the line, which we will split up. */
+    while (ISWHITE(*line))
+       line++;
+    p = xstrdup(line);
+
+    /* Allocate worst-case amount of space. */
+    for (*argvp = argv = xmalloc((strlen(p) + 2) * sizeof(char *)); *p; ) {
+       /* Mark start of this word, find its end. */
+       for (*argv++ = p; *p && !ISWHITE(*p); )
+           p++;
+       if (*p == '\0')
+           break;
+
+       /* Nip off word, skip whitespace. */
+       for (*p++ = '\0'; ISWHITE(*p); )
+           p++;
+    }
+    *argv = NULL;
+    return argv - *argvp;
+}
+
+
+/*
+**  Take a vector which Argify made and glue it back together with
+**  spaces between each element.  Returns a pointer to dynamic space.
+*/
+char *
+Glom(av)
+    char               **av;
+{
+    char       **v;
+    int        i;
+    char               *save;
+
+    /* Get space. */
+    for (i = 0, v = av; *v; v++)
+       i += strlen(*v) + 1;
+    i++;
+
+    save = xmalloc(i);
+    save[0] = '\0';
+    for (v = av; *v; v++) {
+       if (v > av)
+            strlcat(save, " ", i);
+        strlcat(save, *v, i);
+    }
+
+    return save;
+}
+
+
+/*
+**  Match a list of newsgroup specifiers against a list of newsgroups.
+**  func is called to see if there is a match.
+*/
+bool PERMmatch(char **Pats, char **list)
+{
+    int                        i;
+    char               *p;
+    int                 match = false;
+
+    if (Pats == NULL || Pats[0] == NULL)
+       return true;
+
+    for ( ; *list; list++) {
+       for (i = 0; (p = Pats[i]) != NULL; i++) {
+           if (p[0] == '!') {
+               if (uwildmat(*list, ++p))
+                   match = false;
+           }
+           else if (uwildmat(*list, p))
+               match = true;
+       }
+       if (match)
+           /* If we can read it in one group, we can read it, period. */
+           return true;
+    }
+
+    return false;
+}
+
+
+/*
+**  Check to see if user is allowed to see this article by matching
+**  Newsgroups line.
+*/
+bool
+PERMartok(void)
+{
+    static char                **grplist;
+    char               *p, **grp;
+
+    if (!PERMspecified)
+       return false;
+
+    if ((p = GetHeader("Xref")) == NULL) {
+       /* in case article does not include Xref */
+       if ((p = GetHeader("Newsgroups")) != NULL) {
+           if (!NGgetlist(&grplist, p))
+               /* No newgroups or null entry. */
+               return true;
+       } else {
+           return true;
+       }
+    } else {
+       /* skip path element */
+       if ((p = strchr(p, ' ')) == NULL)
+           return true;
+       for (p++ ; *p == ' ' ; p++);
+       if (*p == '\0')
+           return true;
+       if (!NGgetlist(&grplist, p))
+           /* No newgroups or null entry. */
+           return true;
+       /* chop ':' and article number */
+       for (grp = grplist ; *grp != NULL ; grp++) {
+           if ((p = strchr(*grp, ':')) == NULL)
+               return true;
+           *p = '\0';
+       }
+    }
+
+#ifdef DO_PYTHON
+    if (PY_use_dynamic) {
+        char    *reply;
+
+       /* Authorize user at a Python authorization module */
+       if (PY_dynamic(PERMuser, p, false, &reply) < 0) {
+           syslog(L_NOTICE, "PY_dynamic(): authorization skipped due to no Python dynamic method defined.");
+       } else {
+           if (reply != NULL) {
+               syslog(L_TRACE, "PY_dynamic() returned a refuse string for user %s at %s who wants to read %s: %s", PERMuser, ClientHost, p, reply);
+                free(reply);
+               return false;
+           }
+            return true;
+       }
+    }
+#endif /* DO_PYTHON */
+
+    return PERMmatch(PERMreadlist, grplist);
+}
+
+
+/*
+**  Parse a newsgroups line, return true if there were any.
+*/
+bool
+NGgetlist(argvp, list)
+    char               ***argvp;
+    char               *list;
+{
+    char       *p;
+
+    for (p = list; *p; p++)
+       if (*p == ',')
+           *p = ' ';
+
+    return Argify(list, argvp) != 0;
+}
+
+
+/*********************************************************************
+ * POSTING RATE LIMITS - The following code implements posting rate
+ * limits. News clients are indexed by IP number (or PERMuser, see
+ * config file). After a relatively configurable number of posts, the nnrpd
+ * process will sleep for a period of time before posting anything.
+ * 
+ * Each time that IP number posts a message, the time of
+ * posting and the previous sleep time is stored. The new sleep time
+ * is computed based on these values.
+ *
+ * To compute the new sleep time, the previous sleep time is, for most
+ * cases multiplied by a factor (backoff_k). 
+ *
+ * See inn.conf(5) for how this code works
+ *
+ *********************************************************************/
+
+/* Defaults are pass through, i.e. not enabled 
+ * NEW for INN 1.8 - Use the inn.conf file to specify the following:
+ *
+ * backoff_k: <integer>
+ * backoff_postfast: <integer>
+ * backoff_postslow: <integer>
+ * backoff_trigger: <integer>
+ * backoff_db: <path>
+ * backoff_auth: <on|off> 
+ *
+ * You may also specify posting backoffs on a per user basis. To do this
+ * turn on "backoff_auth"
+ *
+ * Now these are runtime constants. <grin>
+ */
+static char postrec_dir[SMBUF];   /* Where is the post record directory? */
+
+void
+InitBackoffConstants()
+{
+  struct stat st;
+
+  /* Default is not to enable this code */
+  BACKOFFenabled = false;
+  
+  /* Read the runtime config file to get parameters */
+
+  if ((PERMaccessconf->backoff_db == NULL) ||
+    !(PERMaccessconf->backoff_k >= 0L && PERMaccessconf->backoff_postfast >= 0L && PERMaccessconf->backoff_postslow >= 1L))
+    return;
+
+  /* Need this database for backing off */
+  strlcpy(postrec_dir, PERMaccessconf->backoff_db, sizeof(postrec_dir));
+  if (stat(postrec_dir, &st) < 0) {
+    if (ENOENT == errno) {
+      if (!MakeDirectory(postrec_dir, true)) {
+       syslog(L_ERROR, "%s cannot create backoff_db '%s': %s",ClientHost,postrec_dir,strerror(errno));
+       return;
+      }
+    } else {
+      syslog(L_ERROR, "%s cannot stat backoff_db '%s': %s",ClientHost,postrec_dir,strerror(errno));
+      return;
+    }
+  }
+  if (!S_ISDIR(st.st_mode)) {
+    syslog(L_ERROR, "%s backoff_db '%s' is not a directory",ClientHost,postrec_dir);
+    return;
+  }
+
+  BACKOFFenabled = true;
+
+  return;
+}
+
+/*
+ * PostRecs are stored in individual files. I didn't have a better
+ * way offhand, don't want to touch DBZ, and the number of posters is
+ * small compared to the number of readers. This is the filename corresponding
+ * to an IP number.
+ */
+char
+*PostRecFilename(ip,user) 
+     char                         *ip;
+     char                         *user;
+{
+     static char                   buff[SPOOLNAMEBUFF];
+     char                          dirbuff[SPOOLNAMEBUFF];
+     struct in_addr                inaddr;
+     unsigned long int             addr;
+     unsigned char                 quads[4];
+     unsigned int                  i;
+
+     if (PERMaccessconf->backoff_auth) {
+       snprintf(buff, sizeof(buff), "%s/%s", postrec_dir, user);
+       return(buff);
+     }
+
+     if (inet_aton(ip, &inaddr) < 1) {
+       /* If inet_aton() fails, we'll assume it's an IPv6 address.  We'll
+        * also assume for now that we're dealing with a limited number of
+        * IPv6 clients so we'll place their files all in the same 
+        * directory for simplicity.  Someday we'll need to change this to
+        * something more scalable such as DBZ when IPv6 clients become
+        * more popular. */
+       snprintf(buff, sizeof(buff), "%s/%s", postrec_dir, ip);
+       return(buff);
+     }
+     /* If it's an IPv4 address just fall through. */
+
+     addr = ntohl(inaddr.s_addr);
+     for (i=0; i<4; i++)
+       quads[i] = (unsigned char) (0xff & (addr>>(i*8)));
+
+     snprintf(dirbuff, sizeof(dirbuff), "%s/%03d%03d/%03d",
+         postrec_dir, quads[3], quads[2], quads[1]);
+     if (!MakeDirectory(dirbuff,true)) {
+       syslog(L_ERROR, "%s Unable to create postrec directories '%s': %s",
+               ClientHost, dirbuff, strerror(errno));
+       return NULL;
+     }
+     snprintf(buff, sizeof(buff), "%s/%03d", dirbuff, quads[0]);
+     return(buff);
+}
+
+/*
+ * Lock the post rec file. Return 1 on lock, 0 on error
+ */
+int
+LockPostRec(path)
+     char              *path;
+{
+  char lockname[SPOOLNAMEBUFF];  
+  char temp[SPOOLNAMEBUFF];
+  int statfailed = 0;
+  snprintf(lockname, sizeof(lockname), "%s.lock", path);
+
+  for (;; sleep(5)) {
+    int fd;
+    struct stat st;
+    time_t now;
+    fd = open(lockname, O_WRONLY|O_EXCL|O_CREAT, 0600);
+    if (fd >= 0) {
+      /* We got the lock! */
+      snprintf(temp, sizeof(temp), "pid:%ld\n", (unsigned long) getpid());
+      write(fd, temp, strlen(temp));
+      close(fd);
+      return(1);
+    }
+
+    /* No lock. See if the file is there. */
+    if (stat(lockname, &st) < 0) {
+      syslog(L_ERROR, "%s cannot stat lock file %s", ClientHost, strerror(errno));
+      if (statfailed++ > 5) return(0);
+      continue;
+    }
+
+    /* If lockfile is older than the value of
+       PERMaccessconf->backoff_postslow, remove it */
+    statfailed = 0;
+    time(&now);
+    if (now < st.st_ctime + PERMaccessconf->backoff_postslow) continue;
+    syslog(L_ERROR, "%s removing stale lock file %s", ClientHost, lockname);
+    unlink(lockname);
+  }
+}
+
+void
+UnlockPostRec(path)
+     char              *path;
+{
+  char lockname[SPOOLNAMEBUFF];  
+
+  snprintf(lockname, sizeof(lockname), "%s.lock", path);
+  if (unlink(lockname) < 0) {
+    syslog(L_ERROR, "%s can't unlink lock file: %s", ClientHost,strerror(errno)) ;
+  }
+  return;
+}
+
+/* 
+ * Get the stored postrecord for that IP 
+ */
+static int
+GetPostRecord(char *path, long *lastpost, long *lastsleep, long *lastn)
+{
+     static char                   buff[SMBUF];
+     FILE                         *fp;
+     char                         *s;
+
+     fp = fopen(path,"r");
+     if (fp == NULL) { 
+       if (errno == ENOENT) {
+         return 1;
+       }
+       syslog(L_ERROR, "%s Error opening '%s': %s",
+              ClientHost, path, strerror(errno));
+       return 0;
+     }
+
+     if (fgets(buff,SMBUF,fp) == NULL) {
+       syslog(L_ERROR, "%s Error reading '%s': %s",
+              ClientHost, path, strerror(errno));
+       return 0;
+     }
+     *lastpost = atol(buff);
+
+     if ((s = strchr(buff,',')) == NULL) {
+       syslog(L_ERROR, "%s bad data in postrec file: '%s'",
+              ClientHost, buff);
+       return 0;
+     }
+     s++; *lastsleep = atol(s);
+
+     if ((s = strchr(s,',')) == NULL) {
+       syslog(L_ERROR, "%s bad data in postrec file: '%s'",
+              ClientHost, buff);
+       return 0;
+     }
+     s++; *lastn = atol(s);
+
+     fclose(fp);
+     return 1;
+}
+
+/* 
+ * Store the postrecord for that IP 
+ */
+static int
+StorePostRecord(char *path, time_t lastpost, long lastsleep, long lastn)
+{
+     FILE                         *fp;
+
+     fp = fopen(path,"w");
+     if (fp == NULL)                   {
+       syslog(L_ERROR, "%s Error opening '%s': %s",
+              ClientHost, path, strerror(errno));
+       return 0;
+     }
+
+     fprintf(fp,"%ld,%ld,%ld\n",(long) lastpost,lastsleep,lastn);
+     fclose(fp);
+     return 1;
+}
+
+/*
+ * Return the proper sleeptime. Return false on error.
+ */
+int
+RateLimit(sleeptime,path) 
+     long                         *sleeptime;
+     char                         *path;
+{
+     TIMEINFO                      Now;
+     long                          prevpost,prevsleep,prevn,n;
+
+     if (GetTimeInfo(&Now) < 0) 
+       return 0;
+     
+     prevpost = 0L; prevsleep = 0L; prevn = 0L; n = 0L;
+     if (!GetPostRecord(path,&prevpost,&prevsleep,&prevn)) {
+       syslog(L_ERROR, "%s can't get post record: %s",
+              ClientHost, strerror(errno));
+       return 0;
+     }
+     /*
+      * Just because yer paranoid doesn't mean they ain't out ta get ya
+      * This is called paranoid clipping
+      */
+     if (prevn < 0L) prevn = 0L;
+     if (prevsleep < 0L)  prevsleep = 0L;
+     if (prevsleep > PERMaccessconf->backoff_postfast)  prevsleep = PERMaccessconf->backoff_postfast;
+     
+      /*
+       * Compute the new sleep time
+       */
+     *sleeptime = 0L;  
+     if (prevpost <= 0L) {
+       prevpost = 0L;
+       prevn = 1L;
+     } else {
+       n = Now.time - prevpost;
+       if (n < 0L) {
+         syslog(L_NOTICE,"%s previous post was in the future (%ld sec)",
+                ClientHost,n);
+         n = 0L;
+       }
+       if (n < PERMaccessconf->backoff_postfast) {
+         if (prevn >= PERMaccessconf->backoff_trigger) {
+           *sleeptime = 1 + (prevsleep * PERMaccessconf->backoff_k);
+         } 
+       } else if (n < PERMaccessconf->backoff_postslow) {
+         if (prevn >= PERMaccessconf->backoff_trigger) {
+           *sleeptime = prevsleep;
+         }
+       } else {
+         prevn = 0L;
+       } 
+       prevn++;
+     }
+
+     *sleeptime = ((*sleeptime) > PERMaccessconf->backoff_postfast) ? PERMaccessconf->backoff_postfast : (*sleeptime);
+     /* This ought to trap this bogon */
+     if ((*sleeptime) < 0L) {
+       syslog(L_ERROR,"%s Negative sleeptime detected: %ld, prevsleep: %ld, N: %ld",ClientHost,*sleeptime,prevsleep,n);
+       *sleeptime = 0L;
+     }
+  
+     /* Store the postrecord */
+     if (!StorePostRecord(path,Now.time,*sleeptime,prevn)) {
+       syslog(L_ERROR, "%s can't store post record: %s", ClientHost, strerror(errno));
+       return 0;
+     }
+
+     return 1;
+}
+
+#ifdef HAVE_SSL
+/*
+**  The "STARTTLS" command.  RFC2595.
+*/
+/* ARGSUSED0 */
+
+void
+CMDstarttls(ac, av)
+    int                ac UNUSED;
+    char       *av[] UNUSED;
+{
+  int result;
+
+  tls_init();
+  if (nnrpd_starttls_done == 1) {
+      Reply("%d Already successfully executed STARTTLS\r\n",
+            NNTP_STARTTLS_DONE_VAL);
+      return;
+  }
+
+  Reply("%d Begin TLS negotiation now\r\n", NNTP_STARTTLS_NEXT_VAL);
+  fflush(stdout);
+
+  /* must flush our buffers before starting tls */
+  
+  result=tls_start_servertls(0, /* read */
+                            1); /* write */
+  if (result==-1) {
+    Reply("%d Starttls failed\r\n", NNTP_STARTTLS_BAD_VAL);
+    return;
+  }
+  nnrpd_starttls_done = 1;
+}
+#endif /* HAVE_SSL */