--- /dev/null
+/* $Id: article.c 7538 2006-08-26 05:44:06Z eagle $
+**
+** Article-related routines.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <assert.h>
+#if HAVE_LIMITS_H
+# include <limits.h>
+#endif
+#include <sys/uio.h>
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "inn/wire.h"
+#include "nnrpd.h"
+#include "ov.h"
+#include "tls.h"
+#include "cache.h"
+
+#ifdef HAVE_SSL
+extern SSL *tls_conn;
+#endif
+
+/*
+** Data structures for use in ARTICLE/HEAD/BODY/STAT common code.
+*/
+typedef enum _SENDTYPE {
+ STarticle,
+ SThead,
+ STbody,
+ STstat
+} SENDTYPE;
+
+typedef struct _SENDDATA {
+ SENDTYPE Type;
+ int ReplyCode;
+ const char *Item;
+} SENDDATA;
+
+static char ARTnotingroup[] = NNTP_NOTINGROUP;
+static char ARTnoartingroup[] = NNTP_NOARTINGRP;
+static char ARTnocurrart[] = NNTP_NOCURRART;
+static ARTHANDLE *ARThandle = NULL;
+static SENDDATA SENDbody = {
+ STbody, NNTP_BODY_FOLLOWS_VAL, "body"
+};
+static SENDDATA SENDarticle = {
+ STarticle, NNTP_ARTICLE_FOLLOWS_VAL, "article"
+};
+static SENDDATA SENDstat = {
+ STstat, NNTP_NOTHING_FOLLOWS_VAL, "status"
+};
+static SENDDATA SENDhead = {
+ SThead, NNTP_HEAD_FOLLOWS_VAL, "head"
+};
+
+
+static struct iovec iov[IOV_MAX > 1024 ? 1024 : IOV_MAX];
+static int queued_iov = 0;
+
+static void PushIOvHelper(struct iovec* vec, int* countp) {
+ int result;
+ TMRstart(TMR_NNTPWRITE);
+#ifdef HAVE_SSL
+ if (tls_conn) {
+Again:
+ result = SSL_writev(tls_conn, vec, *countp);
+ switch (SSL_get_error(tls_conn, result)) {
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_SYSCALL:
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ goto Again;
+ break;
+ case SSL_ERROR_SSL:
+ SSL_shutdown(tls_conn);
+ tls_conn = NULL;
+ errno = ECONNRESET;
+ break;
+ case SSL_ERROR_ZERO_RETURN:
+ break;
+ }
+ } else {
+ result = xwritev(STDOUT_FILENO, vec, *countp);
+ }
+#else
+ result = xwritev(STDOUT_FILENO, vec, *countp);
+#endif
+ TMRstop(TMR_NNTPWRITE);
+ if (result == -1) {
+ /* we can't recover, since we can't resynchronise with our
+ * peer */
+ ExitWithStats(1, true);
+ }
+ *countp = 0;
+}
+
+static void
+PushIOvRateLimited(void) {
+ double start, end, elapsed, target;
+ struct iovec newiov[IOV_MAX > 1024 ? 1024 : IOV_MAX];
+ int newiov_len;
+ int sentiov;
+ int i;
+ int bytesfound;
+ int chunkbittenoff;
+ struct timeval waittime;
+
+ while (queued_iov) {
+ bytesfound = newiov_len = 0;
+ sentiov = 0;
+ for (i = 0; (i < queued_iov) && (bytesfound < MaxBytesPerSecond); i++) {
+ if ((signed)iov[i].iov_len + bytesfound > MaxBytesPerSecond) {
+ chunkbittenoff = MaxBytesPerSecond - bytesfound;
+ newiov[newiov_len].iov_base = iov[i].iov_base;
+ newiov[newiov_len++].iov_len = chunkbittenoff;
+ iov[i].iov_base = (char *)iov[i].iov_base + chunkbittenoff;
+ iov[i].iov_len -= chunkbittenoff;
+ bytesfound += chunkbittenoff;
+ } else {
+ newiov[newiov_len++] = iov[i];
+ sentiov++;
+ bytesfound += iov[i].iov_len;
+ }
+ }
+ assert(sentiov <= queued_iov);
+ start = TMRnow_double();
+ PushIOvHelper(newiov, &newiov_len);
+ end = TMRnow_double();
+ target = (double) bytesfound / MaxBytesPerSecond;
+ elapsed = end - start;
+ if (elapsed < 1 && elapsed < target) {
+ waittime.tv_sec = 0;
+ waittime.tv_usec = (target - elapsed) * 1e6;
+ start = TMRnow_double();
+ if (select(0, NULL, NULL, NULL, &waittime) != 0)
+ syswarn("%s: select in PushIOvRateLimit failed", ClientHost);
+ end = TMRnow_double();
+ IDLEtime += end - start;
+ }
+ memmove(iov, &iov[sentiov], (queued_iov - sentiov) * sizeof(struct iovec));
+ queued_iov -= sentiov;
+ }
+}
+
+static void
+PushIOv(void) {
+ TMRstart(TMR_NNTPWRITE);
+ fflush(stdout);
+ TMRstop(TMR_NNTPWRITE);
+ if (MaxBytesPerSecond != 0)
+ PushIOvRateLimited();
+ else
+ PushIOvHelper(iov, &queued_iov);
+}
+
+static void
+SendIOv(const char *p, int len) {
+ char *q;
+
+ if (queued_iov) {
+ q = (char *)iov[queued_iov - 1].iov_base + iov[queued_iov - 1].iov_len;
+ if (p == q) {
+ iov[queued_iov - 1].iov_len += len;
+ return;
+ }
+ }
+ iov[queued_iov].iov_base = (char*)p;
+ iov[queued_iov++].iov_len = len;
+ if (queued_iov == IOV_MAX)
+ PushIOv();
+}
+
+static char *_IO_buffer_ = NULL;
+static int highwater = 0;
+
+static void
+PushIOb(void) {
+ TMRstart(TMR_NNTPWRITE);
+ fflush(stdout);
+#ifdef HAVE_SSL
+ if (tls_conn) {
+ int r;
+Again:
+ r = SSL_write(tls_conn, _IO_buffer_, highwater);
+ switch (SSL_get_error(tls_conn, r)) {
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_SYSCALL:
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ goto Again;
+ break;
+ case SSL_ERROR_SSL:
+ SSL_shutdown(tls_conn);
+ tls_conn = NULL;
+ errno = ECONNRESET;
+ break;
+ case SSL_ERROR_ZERO_RETURN:
+ break;
+ }
+ if (r != highwater) {
+ TMRstop(TMR_NNTPWRITE);
+ highwater = 0;
+ return;
+ }
+ } else {
+ if (xwrite(STDOUT_FILENO, _IO_buffer_, highwater) != highwater) {
+ TMRstop(TMR_NNTPWRITE);
+ highwater = 0;
+ return;
+ }
+ }
+#else
+ if (xwrite(STDOUT_FILENO, _IO_buffer_, highwater) != highwater) {
+ TMRstop(TMR_NNTPWRITE);
+ highwater = 0;
+ return;
+ }
+#endif
+ TMRstop(TMR_NNTPWRITE);
+ highwater = 0;
+}
+
+static void
+SendIOb(const char *p, int len) {
+ int tocopy;
+
+ if (_IO_buffer_ == NULL)
+ _IO_buffer_ = xmalloc(BIG_BUFFER);
+
+ while (len > 0) {
+ tocopy = (len > (BIG_BUFFER - highwater)) ? (BIG_BUFFER - highwater) : len;
+ memcpy(&_IO_buffer_[highwater], p, tocopy);
+ p += tocopy;
+ highwater += tocopy;
+ len -= tocopy;
+ if (highwater == BIG_BUFFER)
+ PushIOb();
+ }
+}
+
+
+/*
+** If we have an article open, close it.
+*/
+void ARTclose(void)
+{
+ if (ARThandle) {
+ SMfreearticle(ARThandle);
+ ARThandle = NULL;
+ }
+}
+
+bool ARTinstorebytoken(TOKEN token)
+{
+ ARTHANDLE *art;
+ struct timeval stv, etv;
+
+ if (PERMaccessconf->nnrpdoverstats) {
+ gettimeofday(&stv, NULL);
+ }
+ art = SMretrieve(token, RETR_STAT); /* XXX This isn't really overstats, is it? */
+ if (PERMaccessconf->nnrpdoverstats) {
+ gettimeofday(&etv, NULL);
+ OVERartcheck+=(etv.tv_sec - stv.tv_sec) * 1000;
+ OVERartcheck+=(etv.tv_usec - stv.tv_usec) / 1000;
+ }
+ if (art) {
+ SMfreearticle(art);
+ return true;
+ }
+ return false;
+}
+
+/*
+** If the article name is valid, open it and stuff in the ID.
+*/
+static bool ARTopen(ARTNUM artnum)
+{
+ static ARTNUM save_artnum;
+ TOKEN token;
+
+ /* Re-use article if it's the same one. */
+ if (save_artnum == artnum) {
+ if (ARThandle)
+ return true;
+ }
+ ARTclose();
+
+ if (!OVgetartinfo(GRPcur, artnum, &token))
+ return false;
+
+ TMRstart(TMR_READART);
+ ARThandle = SMretrieve(token, RETR_ALL);
+ TMRstop(TMR_READART);
+ if (ARThandle == NULL) {
+ return false;
+ }
+
+ save_artnum = artnum;
+ return true;
+}
+
+
+/*
+** Open the article for a given Message-ID.
+*/
+static bool
+ARTopenbyid(char *msg_id, ARTNUM *ap, bool final)
+{
+ TOKEN token;
+
+ *ap = 0;
+ token = cache_get(HashMessageID(msg_id), final);
+ if (token.type == TOKEN_EMPTY) {
+ if (History == NULL) {
+ time_t statinterval;
+
+ /* Do lazy opens of the history file - lots of clients
+ * will never ask for anything by message id, so put off
+ * doing the work until we have to */
+ History = HISopen(HISTORY, innconf->hismethod, HIS_RDONLY);
+ if (!History) {
+ syslog(L_NOTICE, "cant initialize history");
+ Reply("%d NNTP server unavailable. Try later.\r\n",
+ NNTP_TEMPERR_VAL);
+ ExitWithStats(1, true);
+ }
+ statinterval = 30;
+ HISctl(History, HISCTLS_STATINTERVAL, &statinterval);
+ }
+ if (!HISlookup(History, msg_id, NULL, NULL, NULL, &token))
+ return false;
+ }
+ if (token.type == TOKEN_EMPTY)
+ return false;
+ TMRstart(TMR_READART);
+ ARThandle = SMretrieve(token, RETR_ALL);
+ TMRstop(TMR_READART);
+ if (ARThandle == NULL) {
+ return false;
+ }
+
+ return true;
+}
+
+/*
+** Send a (part of) a file to stdout, doing newline and dot conversion.
+*/
+static void ARTsendmmap(SENDTYPE what)
+{
+ const char *p, *q, *r;
+ const char *s, *path, *xref, *endofpath;
+ long bytecount;
+ char lastchar;
+
+ ARTcount++;
+ GRParticles++;
+ bytecount = 0;
+ lastchar = -1;
+
+ /* Get the headers and detect if wire format. */
+ if (what == STarticle) {
+ q = ARThandle->data;
+ p = ARThandle->data + ARThandle->len;
+ } else {
+ for (q = p = ARThandle->data; p < (ARThandle->data + ARThandle->len); p++) {
+ if (*p == '\r')
+ continue;
+ if (*p == '\n') {
+ if (lastchar == '\n') {
+ if (what == SThead) {
+ if (*(p-1) == '\r')
+ p--;
+ break;
+ } else {
+ q = p + 1;
+ p = ARThandle->data + ARThandle->len;
+ break;
+ }
+ }
+ }
+ lastchar = *p;
+ }
+ }
+
+ /* q points to the start of the article buffer, p to the end of it */
+ if (VirtualPathlen > 0 && (what != STbody)) {
+ path = wire_findheader(ARThandle->data, ARThandle->len, "Path");
+ if (path == NULL) {
+ SendIOv(".\r\n", 3);
+ ARTgetsize += 3;
+ PushIOv();
+ ARTget++;
+ return;
+ } else {
+ xref = wire_findheader(ARThandle->data, ARThandle->len, "Xref");
+ if (xref == NULL) {
+ SendIOv(".\r\n", 3);
+ ARTgetsize += 3;
+ PushIOv();
+ ARTget++;
+ return;
+ }
+ }
+ endofpath = wire_endheader(path, ARThandle->data + ARThandle->len - 1);
+ if (endofpath == NULL) {
+ SendIOv(".\r\n", 3);
+ ARTgetsize += 3;
+ PushIOv();
+ ARTget++;
+ return;
+ }
+ if ((r = memchr(xref, ' ', p - xref)) == NULL || r == p) {
+ SendIOv(".\r\n", 3);
+ ARTgetsize += 3;
+ PushIOv();
+ ARTget++;
+ return;
+ }
+ /* r points to the first space in the Xref header */
+ for (s = path, lastchar = '\0';
+ s + VirtualPathlen + 1 < endofpath;
+ lastchar = *s++) {
+ if ((lastchar != '\0' && lastchar != '!') || *s != *VirtualPath ||
+ strncmp(s, VirtualPath, VirtualPathlen - 1) != 0)
+ continue;
+ if (*(s + VirtualPathlen - 1) != '\0' &&
+ *(s + VirtualPathlen - 1) != '!')
+ continue;
+ break;
+ }
+ if (s + VirtualPathlen + 1 < endofpath) {
+ if (xref > path) {
+ SendIOv(q, path - q);
+ SendIOv(s, xref - s);
+ SendIOv(VirtualPath, VirtualPathlen - 1);
+ SendIOv(r, p - r);
+ } else {
+ SendIOv(q, xref - q);
+ SendIOv(VirtualPath, VirtualPathlen - 1);
+ SendIOv(r, path - r);
+ SendIOv(s, p - s);
+ }
+ } else {
+ if (xref > path) {
+ SendIOv(q, path - q);
+ SendIOv(VirtualPath, VirtualPathlen);
+ SendIOv(path, xref - path);
+ SendIOv(VirtualPath, VirtualPathlen - 1);
+ SendIOv(r, p - r);
+ } else {
+ SendIOv(q, xref - q);
+ SendIOv(VirtualPath, VirtualPathlen - 1);
+ SendIOv(r, path - r);
+ SendIOv(VirtualPath, VirtualPathlen);
+ SendIOv(path, p - path);
+ }
+ }
+ } else
+ SendIOv(q, p - q);
+ ARTgetsize += p - q;
+ if (what == SThead) {
+ SendIOv(".\r\n", 3);
+ ARTgetsize += 3;
+ } else if (memcmp((ARThandle->data + ARThandle->len - 5), "\r\n.\r\n", 5)) {
+ if (memcmp((ARThandle->data + ARThandle->len - 2), "\r\n", 2)) {
+ SendIOv("\r\n.\r\n", 5);
+ ARTgetsize += 5;
+ } else {
+ SendIOv(".\r\n", 3);
+ ARTgetsize += 3;
+ }
+ }
+ PushIOv();
+
+ ARTget++;
+}
+
+/*
+** Return the header from the specified file, or NULL if not found.
+*/
+char *GetHeader(const char *header)
+{
+ const char *p, *q, *r, *s, *t;
+ char *w, prevchar;
+ /* Bogus value here to make sure that it isn't initialized to \n */
+ char lastchar = ' ';
+ const char *limit;
+ const char *cmplimit;
+ static char *retval = NULL;
+ static int retlen = 0;
+ int headerlen;
+ bool pathheader = false;
+ bool xrefheader = false;
+
+ limit = ARThandle->data + ARThandle->len;
+ cmplimit = ARThandle->data + ARThandle->len - strlen(header) - 1;
+ for (p = ARThandle->data; p < cmplimit; p++) {
+ if (*p == '\r')
+ continue;
+ if ((lastchar == '\n') && (*p == '\n')) {
+ return NULL;
+ }
+ if ((lastchar == '\n') || (p == ARThandle->data)) {
+ headerlen = strlen(header);
+ if (strncasecmp(p, header, headerlen) == 0 && p[headerlen] == ':') {
+ for (; (p < limit) && !isspace((int)*p) ; p++);
+ for (; (p < limit) && isspace((int)*p) ; p++);
+ for (q = p; q < limit; q++)
+ if ((*q == '\r') || (*q == '\n')) {
+ /* Check for continuation header lines */
+ t = q + 1;
+ if (t < limit) {
+ if ((*q == '\r' && *t == '\n')) {
+ t++;
+ if (t == limit)
+ break;
+ }
+ if ((*t == '\t' || *t == ' ')) {
+ for (; (t < limit) && isspace((int)*t) ; t++);
+ q = t;
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+ if (q == limit)
+ return NULL;
+ if (strncasecmp("Path", header, headerlen) == 0)
+ pathheader = true;
+ else if (strncasecmp("Xref", header, headerlen) == 0)
+ xrefheader = true;
+ if (retval == NULL) {
+ retlen = q - p + VirtualPathlen + 1;
+ retval = xmalloc(retlen);
+ } else {
+ if ((q - p + VirtualPathlen + 1) > retlen) {
+ retlen = q - p + VirtualPathlen + 1;
+ retval = xrealloc(retval, retlen);
+ }
+ }
+ if (pathheader && (VirtualPathlen > 0)) {
+ const char *endofpath;
+ const char *endofarticle;
+
+ endofarticle = ARThandle->data + ARThandle->len - 1;
+ endofpath = wire_endheader(p, endofarticle);
+ if (endofpath == NULL)
+ return NULL;
+ for (s = p, prevchar = '\0';
+ s + VirtualPathlen + 1 < endofpath;
+ prevchar = *s++) {
+ if ((prevchar != '\0' && prevchar != '!') ||
+ *s != *VirtualPath ||
+ strncmp(s, VirtualPath, VirtualPathlen - 1) != 0)
+ continue;
+ if (*(s + VirtualPathlen - 1) != '\0' &&
+ *(s + VirtualPathlen - 1) != '!')
+ continue;
+ break;
+ }
+ if (s + VirtualPathlen + 1 < endofpath) {
+ memcpy(retval, s, q - s);
+ *(retval + (int)(q - s)) = '\0';
+ } else {
+ memcpy(retval, VirtualPath, VirtualPathlen);
+ memcpy(retval + VirtualPathlen, p, q - p);
+ *(retval + (int)(q - p) + VirtualPathlen) = '\0';
+ }
+ } else if (xrefheader && (VirtualPathlen > 0)) {
+ if ((r = memchr(p, ' ', q - p)) == NULL)
+ return NULL;
+ for (; (r < q) && isspace((int)*r) ; r++);
+ if (r == q)
+ return NULL;
+ memcpy(retval, VirtualPath, VirtualPathlen - 1);
+ memcpy(retval + VirtualPathlen - 1, r - 1, q - r + 1);
+ *(retval + (int)(q - r) + VirtualPathlen) = '\0';
+ } else {
+ memcpy(retval, p, q - p);
+ *(retval + (int)(q - p)) = '\0';
+ }
+ for (w = retval; *w; w++)
+ if (*w == '\n' || *w == '\r')
+ *w = ' ';
+ return retval;
+ }
+ }
+ lastchar = *p;
+ }
+ return NULL;
+}
+
+/*
+** Fetch part or all of an article and send it to the client.
+*/
+void CMDfetch(int ac, char *av[])
+{
+ char buff[SMBUF];
+ SENDDATA *what;
+ bool ok;
+ ARTNUM art;
+ char *msgid;
+ ARTNUM tart;
+ bool final = false;
+
+ /* Find what to send; get permissions. */
+ ok = PERMcanread;
+ switch (*av[0]) {
+ default:
+ what = &SENDbody;
+ final = true;
+ break;
+ case 'a': case 'A':
+ what = &SENDarticle;
+ final = true;
+ break;
+ case 's': case 'S':
+ what = &SENDstat;
+ break;
+ case 'h': case 'H':
+ what = &SENDhead;
+ /* Poster might do a "head" command to verify the article. */
+ ok = PERMcanread || PERMcanpost;
+ break;
+ }
+
+ if (!ok) {
+ Reply("%s\r\n", NOACCESS);
+ return;
+ }
+
+ /* Requesting by Message-ID? */
+ if (ac == 2 && av[1][0] == '<') {
+ if (!ARTopenbyid(av[1], &art, final)) {
+ Reply("%d No such article\r\n", NNTP_DONTHAVEIT_VAL);
+ return;
+ }
+ if (!PERMartok()) {
+ ARTclose();
+ Reply("%s\r\n", NOACCESS);
+ return;
+ }
+ tart=art;
+ Reply("%d %lu %s %s\r\n", what->ReplyCode, (unsigned long) art,
+ av[1], what->Item);
+ if (what->Type != STstat) {
+ ARTsendmmap(what->Type);
+ }
+ ARTclose();
+ return;
+ }
+
+ /* Trying to read. */
+ if (GRPcount == 0) {
+ Reply("%s\r\n", ARTnotingroup);
+ return;
+ }
+
+ /* Default is to get current article, or specified article. */
+ if (ac == 1) {
+ if (ARTnumber < ARTlow || ARTnumber > ARThigh) {
+ Reply("%s\r\n", ARTnocurrart);
+ return;
+ }
+ snprintf(buff, sizeof(buff), "%d", ARTnumber);
+ tart=ARTnumber;
+ }
+ else {
+ if (strspn(av[1], "0123456789") != strlen(av[1])) {
+ Reply("%s\r\n", ARTnoartingroup);
+ return;
+ }
+ strlcpy(buff, av[1], sizeof(buff));
+ tart=(ARTNUM)atol(buff);
+ }
+
+ /* Open the article and send the reply. */
+ if (!ARTopen(atol(buff))) {
+ Reply("%s\r\n", ARTnoartingroup);
+ return;
+ }
+ if (ac > 1)
+ ARTnumber = tart;
+ if ((msgid = GetHeader("Message-ID")) == NULL) {
+ ARTclose();
+ Reply("%s\r\n", ARTnoartingroup);
+ return;
+ }
+ Reply("%d %s %.512s %s\r\n", what->ReplyCode, buff, msgid, what->Item);
+ if (what->Type != STstat)
+ ARTsendmmap(what->Type);
+ ARTclose();
+}
+
+
+/*
+** Go to the next or last (really previous) article in the group.
+*/
+void CMDnextlast(int ac UNUSED, char *av[])
+{
+ char *msgid;
+ int save, delta, errcode;
+ bool next;
+ const char *message;
+
+ if (!PERMcanread) {
+ Reply("%s\r\n", NOACCESS);
+ return;
+ }
+ if (GRPcount == 0) {
+ Reply("%s\r\n", ARTnotingroup);
+ return;
+ }
+ if (ARTnumber < ARTlow || ARTnumber > ARThigh) {
+ Reply("%s\r\n", ARTnocurrart);
+ return;
+ }
+
+ next = (av[0][0] == 'n' || av[0][0] == 'N');
+ if (next) {
+ delta = 1;
+ errcode = NNTP_NONEXT_VAL;
+ message = "next";
+ }
+ else {
+ delta = -1;
+ errcode = NNTP_NOPREV_VAL;
+ message = "previous";
+ }
+
+ save = ARTnumber;
+ msgid = NULL;
+ do {
+ ARTnumber += delta;
+ if (ARTnumber < ARTlow || ARTnumber > ARThigh) {
+ Reply("%d No %s to retrieve.\r\n", errcode, message);
+ ARTnumber = save;
+ return;
+ }
+ if (!ARTopen(ARTnumber))
+ continue;
+ msgid = GetHeader("Message-ID");
+ ARTclose();
+ } while (msgid == NULL);
+
+ Reply("%d %d %s Article retrieved; request text separately.\r\n",
+ NNTP_NOTHING_FOLLOWS_VAL, ARTnumber, msgid);
+}
+
+
+static bool CMDgetrange(int ac, char *av[], ARTRANGE *rp, bool *DidReply)
+{
+ char *p;
+
+ *DidReply = false;
+ if (GRPcount == 0) {
+ Reply("%s\r\n", ARTnotingroup);
+ *DidReply = true;
+ return false;
+ }
+
+ if (ac == 1) {
+ /* No argument, do only current article. */
+ if (ARTnumber < ARTlow || ARTnumber > ARThigh) {
+ Reply("%s\r\n", ARTnocurrart);
+ *DidReply = true;
+ return false;
+ }
+ rp->High = rp->Low = ARTnumber;
+ return true;
+ }
+
+ /* Got just a single number? */
+ if ((p = strchr(av[1], '-')) == NULL) {
+ rp->Low = rp->High = atol(av[1]);
+ return true;
+ }
+
+ /* Parse range. */
+ *p++ = '\0';
+ rp->Low = atol(av[1]);
+ if (*p == '\0' || (rp->High = atol(p)) < rp->Low)
+ /* "XHDR 234-0 header" gives everything to the end. */
+ rp->High = ARThigh;
+ else if (rp->High > ARThigh)
+ rp->High = ARThigh;
+ if (rp->Low < ARTlow)
+ rp->Low = ARTlow;
+ p--;
+ *p = '-';
+
+ return true;
+}
+
+
+/*
+** Apply virtual hosting to an Xref field.
+*/
+static char *
+vhost_xref(char *p)
+{
+ char *space;
+ size_t offset;
+ char *field = NULL;
+
+ space = strchr(p, ' ');
+ if (space == NULL) {
+ warn("malformed Xref `%s'", field);
+ goto fail;
+ }
+ offset = space + 1 - p;
+ space = strchr(p + offset, ' ');
+ if (space == NULL) {
+ warn("malformed Xref `%s'", field);
+ goto fail;
+ }
+ field = concat(PERMaccessconf->domain, space, NULL);
+ fail:
+ free(p);
+ return field;
+}
+
+/*
+** XOVER another extension. Dump parts of the overview database.
+*/
+void CMDxover(int ac, char *av[])
+{
+ bool DidReply;
+ ARTRANGE range;
+ struct timeval stv, etv;
+ ARTNUM artnum;
+ void *handle;
+ char *data, *r;
+ const char *p, *q;
+ int len, useIOb = 0;
+ TOKEN token;
+ struct cvector *vector = NULL;
+
+ if (!PERMcanread) {
+ Printf("%s\r\n", NOACCESS);
+ return;
+ }
+
+ /* Trying to read. */
+ if (GRPcount == 0) {
+ Reply("%s\r\n", ARTnotingroup);
+ return;
+ }
+
+ /* Parse range. */
+ if (!CMDgetrange(ac, av, &range, &DidReply)) {
+ if (!DidReply) {
+ Reply("%d data follows\r\n", NNTP_OVERVIEW_FOLLOWS_VAL);
+ Printf(".\r\n");
+ return;
+ }
+ }
+
+ OVERcount++;
+ gettimeofday(&stv, NULL);
+ if ((handle = (void *)OVopensearch(GRPcur, range.Low, range.High)) == NULL) {
+ if (av[1] != NULL)
+ Reply("%d %s fields follow\r\n.\r\n", NNTP_OVERVIEW_FOLLOWS_VAL, av[1]);
+ else
+ Reply("%d %d fields follow\r\n.\r\n", NNTP_OVERVIEW_FOLLOWS_VAL, ARTnumber);
+ return;
+ }
+ if (PERMaccessconf->nnrpdoverstats) {
+ gettimeofday(&etv, NULL);
+ OVERtime+=(etv.tv_sec - stv.tv_sec) * 1000;
+ OVERtime+=(etv.tv_usec - stv.tv_usec) / 1000;
+ }
+
+ if (av[1] != NULL)
+ Reply("%d %s fields follow\r\n", NNTP_OVERVIEW_FOLLOWS_VAL, av[1]);
+ else
+ Reply("%d %d fields follow\r\n", NNTP_OVERVIEW_FOLLOWS_VAL, ARTnumber);
+ fflush(stdout);
+ if (PERMaccessconf->nnrpdoverstats)
+ gettimeofday(&stv, NULL);
+
+ /* If OVSTATICSEARCH is true, then the data returned by OVsearch is only
+ valid until the next call to OVsearch. In this case, we must use
+ SendIOb because it copies the data. */
+ OVctl(OVSTATICSEARCH, &useIOb);
+
+ while (OVsearch(handle, &artnum, &data, &len, &token, NULL)) {
+ if (PERMaccessconf->nnrpdoverstats) {
+ gettimeofday(&etv, NULL);
+ OVERtime+=(etv.tv_sec - stv.tv_sec) * 1000;
+ OVERtime+=(etv.tv_usec - stv.tv_usec) / 1000;
+ }
+ if (len == 0 || (PERMaccessconf->nnrpdcheckart && !ARTinstorebytoken(token))) {
+ if (PERMaccessconf->nnrpdoverstats) {
+ OVERmiss++;
+ gettimeofday(&stv, NULL);
+ }
+ continue;
+ }
+ if (PERMaccessconf->nnrpdoverstats) {
+ OVERhit++;
+ OVERsize += len;
+ }
+ vector = overview_split(data, len, NULL, vector);
+ r = overview_getheader(vector, OVERVIEW_MESSAGE_ID, OVextra);
+ cache_add(HashMessageID(r), token);
+ free(r);
+ if (VirtualPathlen > 0 && overhdr_xref != -1) {
+ if ((overhdr_xref + 1) >= vector->count)
+ continue;
+ p = vector->strings[overhdr_xref] + sizeof("Xref: ") - 1;
+ while ((p < data + len) && *p == ' ')
+ ++p;
+ q = memchr(p, ' ', data + len - p);
+ if (q == NULL)
+ continue;
+ if(useIOb) {
+ SendIOb(data, p - data);
+ SendIOb(VirtualPath, VirtualPathlen - 1);
+ SendIOb(q, len - (q - data));
+ } else {
+ SendIOv(data, p - data);
+ SendIOv(VirtualPath, VirtualPathlen - 1);
+ SendIOv(q, len - (q - data));
+ }
+ } else {
+ if(useIOb)
+ SendIOb(data, len);
+ else
+ SendIOv(data, len);
+ }
+ if (PERMaccessconf->nnrpdoverstats)
+ gettimeofday(&stv, NULL);
+ }
+
+ if (vector)
+ cvector_free(vector);
+
+ if (PERMaccessconf->nnrpdoverstats) {
+ gettimeofday(&etv, NULL);
+ OVERtime+=(etv.tv_sec - stv.tv_sec) * 1000;
+ OVERtime+=(etv.tv_usec - stv.tv_usec) / 1000;
+ }
+ if(useIOb) {
+ SendIOb(".\r\n", 3);
+ PushIOb();
+ } else {
+ SendIOv(".\r\n", 3);
+ PushIOv();
+ }
+ if (PERMaccessconf->nnrpdoverstats)
+ gettimeofday(&stv, NULL);
+ OVclosesearch(handle);
+ if (PERMaccessconf->nnrpdoverstats) {
+ gettimeofday(&etv, NULL);
+ OVERtime+=(etv.tv_sec - stv.tv_sec) * 1000;
+ OVERtime+=(etv.tv_usec - stv.tv_usec) / 1000;
+ }
+
+}
+
+/*
+** XHDR and XPAT extensions. Note that HDR as specified in the new NNTP
+** draft works differently than XHDR has historically, so don't just use this
+** function to implement it without reviewing the differences.
+*/
+/* ARGSUSED */
+void CMDpat(int ac, char *av[])
+{
+ char *p;
+ int i;
+ ARTRANGE range;
+ bool IsLines;
+ bool DidReply;
+ char *header;
+ char *pattern;
+ char *text;
+ int Overview;
+ ARTNUM artnum;
+ char buff[SPOOLNAMEBUFF];
+ void *handle;
+ char *data;
+ int len;
+ TOKEN token;
+ struct cvector *vector = NULL;
+
+ if (!PERMcanread) {
+ Printf("%s\r\n", NOACCESS);
+ return;
+ }
+
+ header = av[1];
+ IsLines = (strcasecmp(header, "lines") == 0);
+
+ if (ac > 3) /* XPAT */
+ pattern = Glom(&av[3]);
+ else
+ pattern = NULL;
+
+ do {
+ /* Message-ID specified? */
+ if (ac > 2 && av[2][0] == '<') {
+ p = av[2];
+ if (!ARTopenbyid(p, &artnum, false)) {
+ Printf("%d No such article.\r\n", NNTP_DONTHAVEIT_VAL);
+ break;
+ }
+ if (!PERMartok()) {
+ ARTclose();
+ Printf("%s\r\n", NOACCESS);
+ break;
+ }
+
+ Printf("%d %s matches follow (ID)\r\n", NNTP_HEAD_FOLLOWS_VAL,
+ header);
+ if ((text = GetHeader(header)) != NULL
+ && (!pattern || uwildmat_simple(text, pattern)))
+ Printf("%s %s\r\n", p, text);
+
+ ARTclose();
+ Printf(".\r\n");
+ break;
+ }
+
+ if (GRPcount == 0) {
+ Reply("%s\r\n", ARTnotingroup);
+ break;
+ }
+
+ /* Range specified. */
+ if (!CMDgetrange(ac - 1, av + 1, &range, &DidReply)) {
+ if (!DidReply) {
+ Reply("%d %s no matches follow (range)\r\n",
+ NNTP_HEAD_FOLLOWS_VAL, header ? header : "\"\"");
+ Printf(".\r\n");
+ break;
+ }
+ }
+
+ /* In overview? */
+ Overview = overview_index(header, OVextra);
+
+ /* Not in overview, we have to fish headers out from the articles */
+ if (Overview < 0 ) {
+ Reply("%d %s matches follow (art)\r\n", NNTP_HEAD_FOLLOWS_VAL,
+ header);
+ for (i = range.Low; i <= range.High && range.High > 0; i++) {
+ if (!ARTopen(i))
+ continue;
+ p = GetHeader(header);
+ if (p && (!pattern || uwildmat_simple(p, pattern))) {
+ snprintf(buff, sizeof(buff), "%u ", i);
+ SendIOb(buff, strlen(buff));
+ SendIOb(p, strlen(p));
+ SendIOb("\r\n", 2);
+ }
+ ARTclose();
+ }
+ SendIOb(".\r\n", 3);
+ PushIOb();
+ break;
+ }
+
+ /* Okay then, we can grab values from overview. */
+ handle = (void *)OVopensearch(GRPcur, range.Low, range.High);
+ if (handle == NULL) {
+ Reply("%d %s no matches follow (NOV)\r\n.\r\n",
+ NNTP_HEAD_FOLLOWS_VAL, header);
+ break;
+ }
+
+ Printf("%d %s matches follow (NOV)\r\n", NNTP_HEAD_FOLLOWS_VAL,
+ header);
+ while (OVsearch(handle, &artnum, &data, &len, &token, NULL)) {
+ if (len == 0 || (PERMaccessconf->nnrpdcheckart
+ && !ARTinstorebytoken(token)))
+ continue;
+ vector = overview_split(data, len, NULL, vector);
+ p = overview_getheader(vector, Overview, OVextra);
+ if (p != NULL) {
+ if (PERMaccessconf->virtualhost &&
+ Overview == overhdr_xref) {
+ p = vhost_xref(p);
+ if (p == NULL)
+ continue;
+ }
+ if (!pattern || uwildmat_simple(p, pattern)) {
+ snprintf(buff, sizeof(buff), "%lu ", artnum);
+ SendIOb(buff, strlen(buff));
+ SendIOb(p, strlen(p));
+ SendIOb("\r\n", 2);
+ }
+ free(p);
+ }
+ }
+ SendIOb(".\r\n", 3);
+ PushIOb();
+ OVclosesearch(handle);
+ } while (0);
+
+ if (vector)
+ cvector_free(vector);
+
+ if (pattern)
+ free(pattern);
+}