X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?p=inn-innduct.git;a=blobdiff_plain;f=.pc%2Fu_xover_duplicate_reply%2Fnnrpd%2Farticle.c;fp=.pc%2Fu_xover_duplicate_reply%2Fnnrpd%2Farticle.c;h=92d8f76af0dbd9c9979d9d404087b2d190944e5d;hp=0000000000000000000000000000000000000000;hb=8f96ca50aa0f9edfd4cd9597dedeeaea07134f7f;hpb=d5b3cbfbd8f26b8b77ce3ce100a9c13c5a71c8f3 diff --git a/.pc/u_xover_duplicate_reply/nnrpd/article.c b/.pc/u_xover_duplicate_reply/nnrpd/article.c new file mode 100644 index 0000000..92d8f76 --- /dev/null +++ b/.pc/u_xover_duplicate_reply/nnrpd/article.c @@ -0,0 +1,1112 @@ +/* $Id: article.c 7538 2006-08-26 05:44:06Z eagle $ +** +** Article-related routines. +*/ + +#include "config.h" +#include "clibrary.h" +#include +#if HAVE_LIMITS_H +# include +#endif +#include + +#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); +}