chiark / gitweb /
Commit 2.4.5-5 as unpacked
[innduct.git] / nnrpd / article.c
1 /*  $Id: article.c 7538 2006-08-26 05:44:06Z eagle $
2 **
3 **  Article-related routines.
4 */
5
6 #include "config.h"
7 #include "clibrary.h"
8 #include <assert.h>
9 #if HAVE_LIMITS_H
10 # include <limits.h>
11 #endif
12 #include <sys/uio.h>
13
14 #include "inn/innconf.h"
15 #include "inn/messages.h"
16 #include "inn/wire.h"
17 #include "nnrpd.h"
18 #include "ov.h"
19 #include "tls.h"
20 #include "cache.h"
21
22 #ifdef HAVE_SSL
23 extern SSL *tls_conn;
24 #endif 
25
26 /*
27 **  Data structures for use in ARTICLE/HEAD/BODY/STAT common code.
28 */
29 typedef enum _SENDTYPE {
30     STarticle,
31     SThead,
32     STbody,
33     STstat
34 } SENDTYPE;
35
36 typedef struct _SENDDATA {
37     SENDTYPE    Type;
38     int         ReplyCode;
39     const char *Item;
40 } SENDDATA;
41
42 static char             ARTnotingroup[] = NNTP_NOTINGROUP;
43 static char             ARTnoartingroup[] = NNTP_NOARTINGRP;
44 static char             ARTnocurrart[] = NNTP_NOCURRART;
45 static ARTHANDLE        *ARThandle = NULL;
46 static SENDDATA         SENDbody = {
47     STbody,     NNTP_BODY_FOLLOWS_VAL,          "body"
48 };
49 static SENDDATA         SENDarticle = {
50     STarticle,  NNTP_ARTICLE_FOLLOWS_VAL,       "article"
51 };
52 static SENDDATA         SENDstat = {
53     STstat,     NNTP_NOTHING_FOLLOWS_VAL,       "status"
54 };
55 static SENDDATA         SENDhead = {
56     SThead,     NNTP_HEAD_FOLLOWS_VAL,          "head"
57 };
58
59
60 static struct iovec     iov[IOV_MAX > 1024 ? 1024 : IOV_MAX];
61 static int              queued_iov = 0;
62
63 static void PushIOvHelper(struct iovec* vec, int* countp) {
64     int result;
65     TMRstart(TMR_NNTPWRITE);
66 #ifdef HAVE_SSL
67     if (tls_conn) {
68 Again:
69         result = SSL_writev(tls_conn, vec, *countp);
70         switch (SSL_get_error(tls_conn, result)) {
71         case SSL_ERROR_NONE:
72         case SSL_ERROR_SYSCALL:
73             break;
74         case SSL_ERROR_WANT_WRITE:
75             goto Again;
76             break;
77         case SSL_ERROR_SSL:
78             SSL_shutdown(tls_conn);
79             tls_conn = NULL;
80             errno = ECONNRESET;
81             break;
82         case SSL_ERROR_ZERO_RETURN:
83             break;
84         }
85     } else {
86         result = xwritev(STDOUT_FILENO, vec, *countp);
87     }
88 #else
89     result = xwritev(STDOUT_FILENO, vec, *countp);
90 #endif
91     TMRstop(TMR_NNTPWRITE);
92     if (result == -1) {
93         /* we can't recover, since we can't resynchronise with our
94          * peer */
95         ExitWithStats(1, true);
96     }
97     *countp = 0;
98 }
99
100 static void
101 PushIOvRateLimited(void) {
102     double              start, end, elapsed, target;
103     struct iovec        newiov[IOV_MAX > 1024 ? 1024 : IOV_MAX];
104     int                 newiov_len;
105     int                 sentiov;
106     int                 i;
107     int                 bytesfound;
108     int                 chunkbittenoff;
109     struct timeval      waittime;
110
111     while (queued_iov) {
112         bytesfound = newiov_len = 0;
113         sentiov = 0;
114         for (i = 0; (i < queued_iov) && (bytesfound < MaxBytesPerSecond); i++) {
115             if ((signed)iov[i].iov_len + bytesfound > MaxBytesPerSecond) {
116                 chunkbittenoff = MaxBytesPerSecond - bytesfound;
117                 newiov[newiov_len].iov_base = iov[i].iov_base;
118                 newiov[newiov_len++].iov_len = chunkbittenoff;
119                 iov[i].iov_base = (char *)iov[i].iov_base + chunkbittenoff;
120                 iov[i].iov_len -= chunkbittenoff;
121                 bytesfound += chunkbittenoff;
122             } else {
123                 newiov[newiov_len++] = iov[i];
124                 sentiov++;
125                 bytesfound += iov[i].iov_len;
126             }
127         }
128         assert(sentiov <= queued_iov);
129         start = TMRnow_double();
130         PushIOvHelper(newiov, &newiov_len);
131         end = TMRnow_double();
132         target = (double) bytesfound / MaxBytesPerSecond;
133         elapsed = end - start;
134         if (elapsed < 1 && elapsed < target) {
135             waittime.tv_sec = 0;
136             waittime.tv_usec = (target - elapsed) * 1e6;
137             start = TMRnow_double();
138             if (select(0, NULL, NULL, NULL, &waittime) != 0)
139                 syswarn("%s: select in PushIOvRateLimit failed", ClientHost);
140             end = TMRnow_double();
141             IDLEtime += end - start;
142         }
143         memmove(iov, &iov[sentiov], (queued_iov - sentiov) * sizeof(struct iovec));
144         queued_iov -= sentiov;
145     }
146 }
147
148 static void
149 PushIOv(void) {
150     TMRstart(TMR_NNTPWRITE);
151     fflush(stdout);
152     TMRstop(TMR_NNTPWRITE);
153     if (MaxBytesPerSecond != 0)
154         PushIOvRateLimited();
155     else
156         PushIOvHelper(iov, &queued_iov);
157 }
158
159 static void
160 SendIOv(const char *p, int len) {
161     char                *q;
162
163     if (queued_iov) {
164         q = (char *)iov[queued_iov - 1].iov_base + iov[queued_iov - 1].iov_len;
165         if (p == q) {
166             iov[queued_iov - 1].iov_len += len;
167             return;
168         }
169     }
170     iov[queued_iov].iov_base = (char*)p;
171     iov[queued_iov++].iov_len = len;
172     if (queued_iov == IOV_MAX)
173         PushIOv();
174 }
175
176 static char             *_IO_buffer_ = NULL;
177 static int              highwater = 0;
178
179 static void
180 PushIOb(void) {
181     TMRstart(TMR_NNTPWRITE);
182     fflush(stdout);
183 #ifdef HAVE_SSL
184     if (tls_conn) {
185         int r;
186 Again:
187         r = SSL_write(tls_conn, _IO_buffer_, highwater);
188         switch (SSL_get_error(tls_conn, r)) {
189         case SSL_ERROR_NONE:
190         case SSL_ERROR_SYSCALL:
191             break;
192         case SSL_ERROR_WANT_WRITE:
193             goto Again;
194             break;
195         case SSL_ERROR_SSL:
196             SSL_shutdown(tls_conn);
197             tls_conn = NULL;
198             errno = ECONNRESET;
199             break;
200         case SSL_ERROR_ZERO_RETURN:
201             break;
202         }
203         if (r != highwater) {
204             TMRstop(TMR_NNTPWRITE);
205             highwater = 0;
206             return;
207         }
208     } else {
209       if (xwrite(STDOUT_FILENO, _IO_buffer_, highwater) != highwater) {
210         TMRstop(TMR_NNTPWRITE);
211         highwater = 0;
212         return;
213       }
214     }
215 #else
216     if (xwrite(STDOUT_FILENO, _IO_buffer_, highwater) != highwater) {
217         TMRstop(TMR_NNTPWRITE);
218         highwater = 0;
219         return;
220     }
221 #endif
222     TMRstop(TMR_NNTPWRITE);
223     highwater = 0;
224 }
225
226 static void
227 SendIOb(const char *p, int len) {
228     int tocopy;
229     
230     if (_IO_buffer_ == NULL)
231         _IO_buffer_ = xmalloc(BIG_BUFFER);
232
233     while (len > 0) {
234         tocopy = (len > (BIG_BUFFER - highwater)) ? (BIG_BUFFER - highwater) : len;
235         memcpy(&_IO_buffer_[highwater], p, tocopy);
236         p += tocopy;
237         highwater += tocopy;
238         len -= tocopy;
239         if (highwater == BIG_BUFFER)
240             PushIOb();
241     }
242 }
243
244
245 /*
246 **  If we have an article open, close it.
247 */
248 void ARTclose(void)
249 {
250     if (ARThandle) {
251         SMfreearticle(ARThandle);
252         ARThandle = NULL;
253     }
254 }
255
256 bool ARTinstorebytoken(TOKEN token)
257 {
258     ARTHANDLE *art;
259     struct timeval      stv, etv;
260
261     if (PERMaccessconf->nnrpdoverstats) {
262         gettimeofday(&stv, NULL);
263     }
264     art = SMretrieve(token, RETR_STAT); /* XXX This isn't really overstats, is it? */
265     if (PERMaccessconf->nnrpdoverstats) {
266         gettimeofday(&etv, NULL);
267         OVERartcheck+=(etv.tv_sec - stv.tv_sec) * 1000;
268         OVERartcheck+=(etv.tv_usec - stv.tv_usec) / 1000;
269     }
270     if (art) {
271         SMfreearticle(art);
272         return true;
273     } 
274     return false;
275 }
276
277 /*
278 **  If the article name is valid, open it and stuff in the ID.
279 */
280 static bool ARTopen(ARTNUM artnum)
281 {
282     static ARTNUM       save_artnum;
283     TOKEN               token;
284
285     /* Re-use article if it's the same one. */
286     if (save_artnum == artnum) {
287         if (ARThandle)
288             return true;
289     }
290     ARTclose();
291
292     if (!OVgetartinfo(GRPcur, artnum, &token))
293         return false;
294   
295     TMRstart(TMR_READART);
296     ARThandle = SMretrieve(token, RETR_ALL);
297     TMRstop(TMR_READART);
298     if (ARThandle == NULL) {
299         return false;
300     }
301
302     save_artnum = artnum;
303     return true;
304 }
305
306
307 /*
308 **  Open the article for a given Message-ID.
309 */
310 static bool
311 ARTopenbyid(char *msg_id, ARTNUM *ap, bool final)
312 {
313     TOKEN token;
314
315     *ap = 0;
316     token = cache_get(HashMessageID(msg_id), final);
317     if (token.type == TOKEN_EMPTY) {
318         if (History == NULL) {
319             time_t statinterval;
320
321             /* Do lazy opens of the history file - lots of clients
322              * will never ask for anything by message id, so put off
323              * doing the work until we have to */
324             History = HISopen(HISTORY, innconf->hismethod, HIS_RDONLY);
325             if (!History) {
326                 syslog(L_NOTICE, "cant initialize history");
327                 Reply("%d NNTP server unavailable. Try later.\r\n",
328                       NNTP_TEMPERR_VAL);
329                 ExitWithStats(1, true);
330             }
331             statinterval = 30;
332             HISctl(History, HISCTLS_STATINTERVAL, &statinterval);
333         }
334         if (!HISlookup(History, msg_id, NULL, NULL, NULL, &token))
335             return false;
336     }
337     if (token.type == TOKEN_EMPTY)
338         return false;
339     TMRstart(TMR_READART);
340     ARThandle = SMretrieve(token, RETR_ALL);
341     TMRstop(TMR_READART);
342     if (ARThandle == NULL) {
343         return false;
344     }
345
346     return true;
347 }
348
349 /*
350 **  Send a (part of) a file to stdout, doing newline and dot conversion.
351 */
352 static void ARTsendmmap(SENDTYPE what)
353 {
354     const char          *p, *q, *r;
355     const char          *s, *path, *xref, *endofpath;
356     long                bytecount;
357     char                lastchar;
358
359     ARTcount++;
360     GRParticles++;
361     bytecount = 0;
362     lastchar = -1;
363
364     /* Get the headers and detect if wire format. */
365     if (what == STarticle) {
366         q = ARThandle->data;
367         p = ARThandle->data + ARThandle->len;
368      } else {
369         for (q = p = ARThandle->data; p < (ARThandle->data + ARThandle->len); p++) {
370             if (*p == '\r')
371                 continue;
372             if (*p == '\n') {
373                 if (lastchar == '\n') {
374                     if (what == SThead) {
375                         if (*(p-1) == '\r')
376                             p--;
377                         break;
378                     } else {
379                         q = p + 1;
380                         p = ARThandle->data + ARThandle->len;
381                         break;
382                     }
383                 }
384             }
385             lastchar = *p;
386         }
387     }
388
389     /* q points to the start of the article buffer, p to the end of it */
390     if (VirtualPathlen > 0 && (what != STbody)) {
391         path = wire_findheader(ARThandle->data, ARThandle->len, "Path");
392         if (path == NULL) {
393             SendIOv(".\r\n", 3);
394             ARTgetsize += 3;
395             PushIOv();
396             ARTget++;
397             return;
398         } else {
399             xref = wire_findheader(ARThandle->data, ARThandle->len, "Xref");
400             if (xref == NULL) {
401                 SendIOv(".\r\n", 3);
402                 ARTgetsize += 3;
403                 PushIOv();
404                 ARTget++;
405                 return;
406             }
407         }
408         endofpath = wire_endheader(path, ARThandle->data + ARThandle->len - 1);
409         if (endofpath == NULL) {
410             SendIOv(".\r\n", 3);
411             ARTgetsize += 3;
412             PushIOv();
413             ARTget++;
414             return;
415         }
416         if ((r = memchr(xref, ' ', p - xref)) == NULL || r == p) {
417             SendIOv(".\r\n", 3);
418             ARTgetsize += 3;
419             PushIOv();
420             ARTget++;
421             return;
422         }
423         /* r points to the first space in the Xref header */
424         for (s = path, lastchar = '\0';
425             s + VirtualPathlen + 1 < endofpath;
426             lastchar = *s++) {
427             if ((lastchar != '\0' && lastchar != '!') || *s != *VirtualPath ||
428                 strncmp(s, VirtualPath, VirtualPathlen - 1) != 0)
429                 continue;
430             if (*(s + VirtualPathlen - 1) != '\0' &&
431                 *(s + VirtualPathlen - 1) != '!')
432                 continue;
433             break;
434         }
435         if (s + VirtualPathlen + 1 < endofpath) {
436             if (xref > path) {
437                 SendIOv(q, path - q);
438                 SendIOv(s, xref - s);
439                 SendIOv(VirtualPath, VirtualPathlen - 1);
440                 SendIOv(r, p - r);
441             } else {
442                 SendIOv(q, xref - q);
443                 SendIOv(VirtualPath, VirtualPathlen - 1);
444                 SendIOv(r, path - r);
445                 SendIOv(s, p - s);
446             }
447         } else {
448             if (xref > path) {
449                 SendIOv(q, path - q);
450                 SendIOv(VirtualPath, VirtualPathlen);
451                 SendIOv(path, xref - path);
452                 SendIOv(VirtualPath, VirtualPathlen - 1);
453                 SendIOv(r, p - r);
454             } else {
455                 SendIOv(q, xref - q);
456                 SendIOv(VirtualPath, VirtualPathlen - 1);
457                 SendIOv(r, path - r);
458                 SendIOv(VirtualPath, VirtualPathlen);
459                 SendIOv(path, p - path);
460             }
461         }
462     } else
463         SendIOv(q, p - q);
464     ARTgetsize += p - q;
465     if (what == SThead) {
466         SendIOv(".\r\n", 3);
467         ARTgetsize += 3;
468     } else if (memcmp((ARThandle->data + ARThandle->len - 5), "\r\n.\r\n", 5)) {
469         if (memcmp((ARThandle->data + ARThandle->len - 2), "\r\n", 2)) {
470             SendIOv("\r\n.\r\n", 5);
471             ARTgetsize += 5;
472         } else {
473             SendIOv(".\r\n", 3);
474             ARTgetsize += 3;
475         }
476     }
477     PushIOv();
478
479     ARTget++;
480 }
481
482 /*
483 **  Return the header from the specified file, or NULL if not found.
484 */
485 char *GetHeader(const char *header)
486 {
487     const char          *p, *q, *r, *s, *t;
488     char                *w, prevchar;
489     /* Bogus value here to make sure that it isn't initialized to \n */
490     char                lastchar = ' ';
491     const char          *limit;
492     const char          *cmplimit;
493     static char         *retval = NULL;
494     static int          retlen = 0;
495     int                 headerlen;
496     bool                pathheader = false;
497     bool                xrefheader = false;
498
499     limit = ARThandle->data + ARThandle->len;
500     cmplimit = ARThandle->data + ARThandle->len - strlen(header) - 1;
501     for (p = ARThandle->data; p < cmplimit; p++) {
502         if (*p == '\r')
503             continue;
504         if ((lastchar == '\n') && (*p == '\n')) {
505             return NULL;
506         }
507         if ((lastchar == '\n') || (p == ARThandle->data)) {
508             headerlen = strlen(header);
509             if (strncasecmp(p, header, headerlen) == 0 && p[headerlen] == ':') {
510                 for (; (p < limit) && !isspace((int)*p) ; p++);
511                 for (; (p < limit) && isspace((int)*p) ; p++);
512                 for (q = p; q < limit; q++) 
513                     if ((*q == '\r') || (*q == '\n')) {
514                         /* Check for continuation header lines */
515                         t = q + 1;
516                         if (t < limit) {
517                             if ((*q == '\r' && *t == '\n')) {
518                                 t++;
519                                 if (t == limit)
520                                     break;
521                             }
522                             if ((*t == '\t' || *t == ' ')) {
523                                 for (; (t < limit) && isspace((int)*t) ; t++);
524                                 q = t;
525                             } else {
526                                 break;
527                             }
528                         } else {
529                             break;
530                         }
531                     }
532                 if (q == limit)
533                     return NULL;
534                 if (strncasecmp("Path", header, headerlen) == 0)
535                     pathheader = true;
536                 else if (strncasecmp("Xref", header, headerlen) == 0)
537                     xrefheader = true;
538                 if (retval == NULL) {
539                     retlen = q - p + VirtualPathlen + 1;
540                     retval = xmalloc(retlen);
541                 } else {
542                     if ((q - p + VirtualPathlen + 1) > retlen) {
543                         retlen = q - p + VirtualPathlen + 1;
544                         retval = xrealloc(retval, retlen);
545                     }
546                 }
547                 if (pathheader && (VirtualPathlen > 0)) {
548                     const char *endofpath;
549                     const char *endofarticle;
550
551                     endofarticle = ARThandle->data + ARThandle->len - 1;
552                     endofpath = wire_endheader(p, endofarticle);
553                     if (endofpath == NULL)
554                         return NULL;
555                     for (s = p, prevchar = '\0';
556                         s + VirtualPathlen + 1 < endofpath;
557                         prevchar = *s++) {
558                         if ((prevchar != '\0' && prevchar != '!') ||
559                             *s != *VirtualPath ||
560                             strncmp(s, VirtualPath, VirtualPathlen - 1) != 0)
561                             continue;
562                         if (*(s + VirtualPathlen - 1) != '\0' &&
563                             *(s + VirtualPathlen - 1) != '!')
564                             continue;
565                         break;
566                     }
567                     if (s + VirtualPathlen + 1 < endofpath) {
568                         memcpy(retval, s, q - s);
569                         *(retval + (int)(q - s)) = '\0';
570                     } else {
571                         memcpy(retval, VirtualPath, VirtualPathlen);
572                         memcpy(retval + VirtualPathlen, p, q - p);
573                         *(retval + (int)(q - p) + VirtualPathlen) = '\0';
574                     }
575                 } else if (xrefheader && (VirtualPathlen > 0)) {
576                     if ((r = memchr(p, ' ', q - p)) == NULL)
577                         return NULL;
578                     for (; (r < q) && isspace((int)*r) ; r++);
579                     if (r == q)
580                         return NULL;
581                     memcpy(retval, VirtualPath, VirtualPathlen - 1);
582                     memcpy(retval + VirtualPathlen - 1, r - 1, q - r + 1);
583                     *(retval + (int)(q - r) + VirtualPathlen) = '\0';
584                 } else {
585                     memcpy(retval, p, q - p);
586                     *(retval + (int)(q - p)) = '\0';
587                 }
588                 for (w = retval; *w; w++)
589                     if (*w == '\n' || *w == '\r')
590                         *w = ' ';
591                 return retval;
592             }
593         }
594         lastchar = *p;
595     }
596     return NULL;
597 }
598
599 /*
600 **  Fetch part or all of an article and send it to the client.
601 */
602 void CMDfetch(int ac, char *av[])
603 {
604     char                buff[SMBUF];
605     SENDDATA            *what;
606     bool                ok;
607     ARTNUM              art;
608     char                *msgid;
609     ARTNUM              tart;
610     bool final = false;
611
612     /* Find what to send; get permissions. */
613     ok = PERMcanread;
614     switch (*av[0]) {
615     default:
616         what = &SENDbody;
617         final = true;
618         break;
619     case 'a': case 'A':
620         what = &SENDarticle;
621         final = true;
622         break;
623     case 's': case 'S':
624         what = &SENDstat;
625         break;
626     case 'h': case 'H':
627         what = &SENDhead;
628         /* Poster might do a "head" command to verify the article. */
629         ok = PERMcanread || PERMcanpost;
630         break;
631     }
632
633     if (!ok) {
634         Reply("%s\r\n", NOACCESS);
635         return;
636     }
637
638     /* Requesting by Message-ID? */
639     if (ac == 2 && av[1][0] == '<') {
640         if (!ARTopenbyid(av[1], &art, final)) {
641             Reply("%d No such article\r\n", NNTP_DONTHAVEIT_VAL);
642             return;
643         }
644         if (!PERMartok()) {
645             ARTclose();
646             Reply("%s\r\n", NOACCESS);
647             return;
648         }
649         tart=art;
650         Reply("%d %lu %s %s\r\n", what->ReplyCode, (unsigned long) art,
651               av[1], what->Item);
652         if (what->Type != STstat) {
653             ARTsendmmap(what->Type);
654         }
655         ARTclose();
656         return;
657     }
658
659     /* Trying to read. */
660     if (GRPcount == 0) {
661         Reply("%s\r\n", ARTnotingroup);
662         return;
663     }
664
665     /* Default is to get current article, or specified article. */
666     if (ac == 1) {
667         if (ARTnumber < ARTlow || ARTnumber > ARThigh) {
668             Reply("%s\r\n", ARTnocurrart);
669             return;
670         }
671         snprintf(buff, sizeof(buff), "%d", ARTnumber);
672         tart=ARTnumber;
673     }
674     else {
675         if (strspn(av[1], "0123456789") != strlen(av[1])) {
676             Reply("%s\r\n", ARTnoartingroup);
677             return;
678         }
679         strlcpy(buff, av[1], sizeof(buff));
680         tart=(ARTNUM)atol(buff);
681     }
682
683     /* Open the article and send the reply. */
684     if (!ARTopen(atol(buff))) {
685         Reply("%s\r\n", ARTnoartingroup);
686         return;
687     }
688     if (ac > 1)
689         ARTnumber = tart;
690     if ((msgid = GetHeader("Message-ID")) == NULL) {
691         Reply("%s\r\n", ARTnoartingroup);
692         return;
693     }
694     Reply("%d %s %.512s %s\r\n", what->ReplyCode, buff, msgid, what->Item); 
695     if (what->Type != STstat)
696         ARTsendmmap(what->Type);
697     ARTclose();
698 }
699
700
701 /*
702 **  Go to the next or last (really previous) article in the group.
703 */
704 void CMDnextlast(int ac UNUSED, char *av[])
705 {
706     char *msgid;
707     int save, delta, errcode;
708     bool next;
709     const char *message;
710
711     if (!PERMcanread) {
712         Reply("%s\r\n", NOACCESS);
713         return;
714     }
715     if (GRPcount == 0) {
716         Reply("%s\r\n", ARTnotingroup);
717         return;
718     }
719     if (ARTnumber < ARTlow || ARTnumber > ARThigh) {
720         Reply("%s\r\n", ARTnocurrart);
721         return;
722     }
723
724     next = (av[0][0] == 'n' || av[0][0] == 'N');
725     if (next) {
726         delta = 1;
727         errcode = NNTP_NONEXT_VAL;
728         message = "next";
729     }
730     else {
731         delta = -1;
732         errcode = NNTP_NOPREV_VAL;
733         message = "previous";
734     }
735
736     save = ARTnumber;
737     msgid = NULL;
738     do {
739         ARTnumber += delta;
740         if (ARTnumber < ARTlow || ARTnumber > ARThigh) {
741             Reply("%d No %s to retrieve.\r\n", errcode, message);
742             ARTnumber = save;
743             return;
744         }
745         if (!ARTopen(ARTnumber))
746             continue;
747         msgid = GetHeader("Message-ID");
748     } while (msgid == NULL);
749
750     ARTclose();
751     Reply("%d %d %s Article retrieved; request text separately.\r\n",
752            NNTP_NOTHING_FOLLOWS_VAL, ARTnumber, msgid);
753 }
754
755
756 static bool CMDgetrange(int ac, char *av[], ARTRANGE *rp, bool *DidReply)
757 {
758     char                *p;
759
760     *DidReply = false;
761     if (GRPcount == 0) {
762         Reply("%s\r\n", ARTnotingroup);
763         *DidReply = true;
764         return false;
765     }
766
767     if (ac == 1) {
768         /* No argument, do only current article. */
769         if (ARTnumber < ARTlow || ARTnumber > ARThigh) {
770             Reply("%s\r\n", ARTnocurrart);
771             *DidReply = true;
772             return false;
773         }
774         rp->High = rp->Low = ARTnumber;
775         return true;
776     }
777
778     /* Got just a single number? */
779     if ((p = strchr(av[1], '-')) == NULL) {
780         rp->Low = rp->High = atol(av[1]);
781         return true;
782     }
783
784     /* Parse range. */
785     *p++ = '\0';
786     rp->Low = atol(av[1]);
787         if (*p == '\0' || (rp->High = atol(p)) < rp->Low)
788             /* "XHDR 234-0 header" gives everything to the end. */
789         rp->High = ARThigh;
790     else if (rp->High > ARThigh)
791         rp->High = ARThigh;
792     if (rp->Low < ARTlow)
793         rp->Low = ARTlow;
794     p--;
795     *p = '-';
796
797     return true;
798 }
799
800
801 /*
802 **  Apply virtual hosting to an Xref field.
803 */
804 static char *
805 vhost_xref(char *p)
806 {
807     char *space;
808     size_t offset;
809     char *field = NULL;
810
811     space = strchr(p, ' ');
812     if (space == NULL) {
813         warn("malformed Xref `%s'", field);
814         goto fail;
815     }
816     offset = space + 1 - p;
817     space = strchr(p + offset, ' ');
818     if (space == NULL) {
819         warn("malformed Xref `%s'", field);
820         goto fail;
821     }
822     field = concat(PERMaccessconf->domain, space, NULL);
823  fail:
824     free(p);
825     return field;
826 }
827
828 /*
829 **  XOVER another extension.  Dump parts of the overview database.
830 */
831 void CMDxover(int ac, char *av[])
832 {
833     bool                DidReply;
834     ARTRANGE            range;
835     struct timeval      stv, etv;
836     ARTNUM              artnum;
837     void                *handle;
838     char                *data, *r;
839     const char          *p, *q;
840     int                 len, useIOb = 0;
841     TOKEN               token;
842     struct cvector *vector = NULL;
843
844     if (!PERMcanread) {
845         Printf("%s\r\n", NOACCESS);
846         return;
847     }
848
849     /* Trying to read. */
850     if (GRPcount == 0) {
851         Reply("%s\r\n", ARTnotingroup);
852         return;
853     }
854
855     /* Parse range. */
856     if (!CMDgetrange(ac, av, &range, &DidReply)) {
857         if (!DidReply) {
858             Reply("%d data follows\r\n", NNTP_OVERVIEW_FOLLOWS_VAL);
859             Printf(".\r\n");
860             return;
861         }
862     }
863
864     OVERcount++;
865     gettimeofday(&stv, NULL);
866     if ((handle = (void *)OVopensearch(GRPcur, range.Low, range.High)) == NULL) {
867         if (av[1] != NULL)
868             Reply("%d %s fields follow\r\n.\r\n", NNTP_OVERVIEW_FOLLOWS_VAL, av[1]);
869         else
870             Reply("%d %d fields follow\r\n.\r\n", NNTP_OVERVIEW_FOLLOWS_VAL, ARTnumber);
871         return;
872     }
873     if (PERMaccessconf->nnrpdoverstats) {
874         gettimeofday(&etv, NULL);
875         OVERtime+=(etv.tv_sec - stv.tv_sec) * 1000;
876         OVERtime+=(etv.tv_usec - stv.tv_usec) / 1000;
877     }
878
879     if (av[1] != NULL)
880         Reply("%d %s fields follow\r\n", NNTP_OVERVIEW_FOLLOWS_VAL, av[1]);
881     else
882         Reply("%d %d fields follow\r\n", NNTP_OVERVIEW_FOLLOWS_VAL, ARTnumber);
883     fflush(stdout);
884     if (PERMaccessconf->nnrpdoverstats)
885         gettimeofday(&stv, NULL);
886
887     /* If OVSTATICSEARCH is true, then the data returned by OVsearch is only
888        valid until the next call to OVsearch.  In this case, we must use
889        SendIOb because it copies the data. */
890     OVctl(OVSTATICSEARCH, &useIOb);
891
892     while (OVsearch(handle, &artnum, &data, &len, &token, NULL)) {
893         if (PERMaccessconf->nnrpdoverstats) {
894             gettimeofday(&etv, NULL);
895             OVERtime+=(etv.tv_sec - stv.tv_sec) * 1000;
896             OVERtime+=(etv.tv_usec - stv.tv_usec) / 1000;
897         }
898         if (len == 0 || (PERMaccessconf->nnrpdcheckart && !ARTinstorebytoken(token))) {
899             if (PERMaccessconf->nnrpdoverstats) {
900                 OVERmiss++;
901                 gettimeofday(&stv, NULL);
902             }
903             continue;
904         }
905         if (PERMaccessconf->nnrpdoverstats) {
906             OVERhit++;
907             OVERsize += len;
908         }
909         vector = overview_split(data, len, NULL, vector);
910         r = overview_getheader(vector, OVERVIEW_MESSAGE_ID, OVextra);
911         cache_add(HashMessageID(r), token);
912         free(r);
913         if (VirtualPathlen > 0 && overhdr_xref != -1) {
914             if ((overhdr_xref + 1) >= vector->count)
915                 continue;
916             p = vector->strings[overhdr_xref] + sizeof("Xref: ") - 1;
917             while ((p < data + len) && *p == ' ')
918                 ++p;
919             q = memchr(p, ' ', data + len - p);
920             if (q == NULL)
921                 continue;
922             if(useIOb) {
923                 SendIOb(data, p - data);
924                 SendIOb(VirtualPath, VirtualPathlen - 1);
925                 SendIOb(q, len - (q - data));
926             } else {
927                 SendIOv(data, p - data);
928                 SendIOv(VirtualPath, VirtualPathlen - 1);
929                 SendIOv(q, len - (q - data));
930             }
931         } else {
932             if(useIOb)
933                 SendIOb(data, len);
934             else
935                 SendIOv(data, len);
936         }
937         if (PERMaccessconf->nnrpdoverstats)
938             gettimeofday(&stv, NULL);
939     }
940
941     if (vector)
942         cvector_free(vector);
943
944     if (PERMaccessconf->nnrpdoverstats) {
945         gettimeofday(&etv, NULL);
946         OVERtime+=(etv.tv_sec - stv.tv_sec) * 1000;
947         OVERtime+=(etv.tv_usec - stv.tv_usec) / 1000;
948     }
949     if(useIOb) {
950         SendIOb(".\r\n", 3);
951         PushIOb();
952     } else {
953         SendIOv(".\r\n", 3);
954         PushIOv();
955     }
956     if (PERMaccessconf->nnrpdoverstats)
957         gettimeofday(&stv, NULL);
958     OVclosesearch(handle);
959     if (PERMaccessconf->nnrpdoverstats) {
960         gettimeofday(&etv, NULL);
961         OVERtime+=(etv.tv_sec - stv.tv_sec) * 1000;
962         OVERtime+=(etv.tv_usec - stv.tv_usec) / 1000;
963     }
964
965 }
966
967 /*
968 **  XHDR and XPAT extensions.  Note that HDR as specified in the new NNTP
969 **  draft works differently than XHDR has historically, so don't just use this
970 **  function to implement it without reviewing the differences.
971 */
972 /* ARGSUSED */
973 void CMDpat(int ac, char *av[])
974 {
975     char                *p;
976     int                 i;
977     ARTRANGE            range;
978     bool                IsLines;
979     bool                DidReply;
980     char                *header;
981     char                *pattern;
982     char                *text;
983     int                 Overview;
984     ARTNUM              artnum;
985     char                buff[SPOOLNAMEBUFF];
986     void                *handle;
987     char                *data;
988     int                 len;
989     TOKEN               token;
990     struct cvector *vector = NULL;
991
992     if (!PERMcanread) {
993         Printf("%s\r\n", NOACCESS);
994         return;
995     }
996
997     header = av[1];
998     IsLines = (strcasecmp(header, "lines") == 0);
999
1000     if (ac > 3) /* XPAT */
1001         pattern = Glom(&av[3]);
1002     else
1003         pattern = NULL;
1004
1005     do {
1006         /* Message-ID specified? */
1007         if (ac > 2 && av[2][0] == '<') {
1008             p = av[2];
1009             if (!ARTopenbyid(p, &artnum, false)) {
1010                 Printf("%d No such article.\r\n", NNTP_DONTHAVEIT_VAL);
1011                 break;
1012             }
1013             Printf("%d %s matches follow (ID)\r\n", NNTP_HEAD_FOLLOWS_VAL,
1014                    header);
1015             if ((text = GetHeader(header)) != NULL
1016                 && (!pattern || uwildmat_simple(text, pattern)))
1017                 Printf("%s %s\r\n", p, text);
1018
1019             ARTclose();
1020             Printf(".\r\n");
1021             break;
1022         }
1023
1024         if (GRPcount == 0) {
1025             Reply("%s\r\n", ARTnotingroup);
1026             break;
1027         }
1028
1029         /* Range specified. */
1030         if (!CMDgetrange(ac - 1, av + 1, &range, &DidReply)) {
1031             if (!DidReply) {
1032                 Reply("%d %s no matches follow (range)\r\n",
1033                       NNTP_HEAD_FOLLOWS_VAL, header ? header : "\"\"");
1034                 Printf(".\r\n");
1035                 break;
1036             }
1037         }
1038
1039         /* In overview? */
1040         Overview = overview_index(header, OVextra);
1041
1042         /* Not in overview, we have to fish headers out from the articles */
1043         if (Overview < 0 ) {
1044             Reply("%d %s matches follow (art)\r\n", NNTP_HEAD_FOLLOWS_VAL,
1045                   header);
1046             for (i = range.Low; i <= range.High && range.High > 0; i++) {
1047                 if (!ARTopen(i))
1048                     continue;
1049                 p = GetHeader(header);
1050                 if (p && (!pattern || uwildmat_simple(p, pattern))) {
1051                     snprintf(buff, sizeof(buff), "%u ", i);
1052                     SendIOb(buff, strlen(buff));
1053                     SendIOb(p, strlen(p));
1054                     SendIOb("\r\n", 2);
1055                     ARTclose();
1056                 }
1057             }
1058             SendIOb(".\r\n", 3);
1059             PushIOb();
1060             break;
1061         }
1062
1063         /* Okay then, we can grab values from overview. */
1064         handle = (void *)OVopensearch(GRPcur, range.Low, range.High);
1065         if (handle == NULL) {
1066             Reply("%d %s no matches follow (NOV)\r\n.\r\n",
1067                   NNTP_HEAD_FOLLOWS_VAL, header);
1068             break;
1069         }       
1070         
1071         Printf("%d %s matches follow (NOV)\r\n", NNTP_HEAD_FOLLOWS_VAL,
1072                header);
1073         while (OVsearch(handle, &artnum, &data, &len, &token, NULL)) {
1074             if (len == 0 || (PERMaccessconf->nnrpdcheckart
1075                 && !ARTinstorebytoken(token)))
1076                 continue;
1077             vector = overview_split(data, len, NULL, vector);
1078             p = overview_getheader(vector, Overview, OVextra);
1079             if (p != NULL) {
1080                 if (PERMaccessconf->virtualhost &&
1081                            Overview == overhdr_xref) {
1082                     p = vhost_xref(p);
1083                     if (p == NULL)
1084                         continue;
1085                 }
1086                 if (!pattern || uwildmat_simple(p, pattern)) {
1087                     snprintf(buff, sizeof(buff), "%lu ", artnum);
1088                     SendIOb(buff, strlen(buff));
1089                     SendIOb(p, strlen(p));
1090                     SendIOb("\r\n", 2);
1091                 }
1092                 free(p);
1093             }
1094         }
1095         SendIOb(".\r\n", 3);
1096         PushIOb();
1097         OVclosesearch(handle);
1098     } while (0);
1099
1100     if (vector)
1101         cvector_free(vector);
1102
1103     if (pattern)
1104         free(pattern);
1105 }