+++ /dev/null
-/* $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) {
- /* No reply because we have already sent NNTP_STARTTLS_NEXT_VAL. */
- return;
- }
- nnrpd_starttls_done = 1;
-}
-#endif /* HAVE_SSL */