chiark / gitweb /
use libinn logging where applicable - compiles
[inn-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         ARTclose();
692         Reply("%s\r\n", ARTnoartingroup);
693         return;
694     }
695     Reply("%d %s %.512s %s\r\n", what->ReplyCode, buff, msgid, what->Item); 
696     if (what->Type != STstat)
697         ARTsendmmap(what->Type);
698     ARTclose();
699 }
700
701
702 /*
703 **  Go to the next or last (really previous) article in the group.
704 */
705 void CMDnextlast(int ac UNUSED, char *av[])
706 {
707     char *msgid;
708     int save, delta, errcode;
709     bool next;
710     const char *message;
711
712     if (!PERMcanread) {
713         Reply("%s\r\n", NOACCESS);
714         return;
715     }
716     if (GRPcount == 0) {
717         Reply("%s\r\n", ARTnotingroup);
718         return;
719     }
720     if (ARTnumber < ARTlow || ARTnumber > ARThigh) {
721         Reply("%s\r\n", ARTnocurrart);
722         return;
723     }
724
725     next = (av[0][0] == 'n' || av[0][0] == 'N');
726     if (next) {
727         delta = 1;
728         errcode = NNTP_NONEXT_VAL;
729         message = "next";
730     }
731     else {
732         delta = -1;
733         errcode = NNTP_NOPREV_VAL;
734         message = "previous";
735     }
736
737     save = ARTnumber;
738     msgid = NULL;
739     do {
740         ARTnumber += delta;
741         if (ARTnumber < ARTlow || ARTnumber > ARThigh) {
742             Reply("%d No %s to retrieve.\r\n", errcode, message);
743             ARTnumber = save;
744             return;
745         }
746         if (!ARTopen(ARTnumber))
747             continue;
748         msgid = GetHeader("Message-ID");
749         ARTclose();
750     } while (msgid == NULL);
751
752     Reply("%d %d %s Article retrieved; request text separately.\r\n",
753            NNTP_NOTHING_FOLLOWS_VAL, ARTnumber, msgid);
754 }
755
756
757 static bool CMDgetrange(int ac, char *av[], ARTRANGE *rp, bool *DidReply)
758 {
759     char                *p;
760
761     *DidReply = false;
762     if (GRPcount == 0) {
763         Reply("%s\r\n", ARTnotingroup);
764         *DidReply = true;
765         return false;
766     }
767
768     if (ac == 1) {
769         /* No argument, do only current article. */
770         if (ARTnumber < ARTlow || ARTnumber > ARThigh) {
771             Reply("%s\r\n", ARTnocurrart);
772             *DidReply = true;
773             return false;
774         }
775         rp->High = rp->Low = ARTnumber;
776         return true;
777     }
778
779     /* Got just a single number? */
780     if ((p = strchr(av[1], '-')) == NULL) {
781         rp->Low = rp->High = atol(av[1]);
782         return true;
783     }
784
785     /* Parse range. */
786     *p++ = '\0';
787     rp->Low = atol(av[1]);
788         if (*p == '\0' || (rp->High = atol(p)) < rp->Low)
789             /* "XHDR 234-0 header" gives everything to the end. */
790         rp->High = ARThigh;
791     else if (rp->High > ARThigh)
792         rp->High = ARThigh;
793     if (rp->Low < ARTlow)
794         rp->Low = ARTlow;
795     p--;
796     *p = '-';
797
798     return true;
799 }
800
801
802 /*
803 **  Apply virtual hosting to an Xref field.
804 */
805 static char *
806 vhost_xref(char *p)
807 {
808     char *space;
809     size_t offset;
810     char *field = NULL;
811
812     space = strchr(p, ' ');
813     if (space == NULL) {
814         warn("malformed Xref `%s'", field);
815         goto fail;
816     }
817     offset = space + 1 - p;
818     space = strchr(p + offset, ' ');
819     if (space == NULL) {
820         warn("malformed Xref `%s'", field);
821         goto fail;
822     }
823     field = concat(PERMaccessconf->domain, space, NULL);
824  fail:
825     free(p);
826     return field;
827 }
828
829 /*
830 **  XOVER another extension.  Dump parts of the overview database.
831 */
832 void CMDxover(int ac, char *av[])
833 {
834     bool                DidReply;
835     ARTRANGE            range;
836     struct timeval      stv, etv;
837     ARTNUM              artnum;
838     void                *handle;
839     char                *data, *r;
840     const char          *p, *q;
841     int                 len, useIOb = 0;
842     TOKEN               token;
843     struct cvector *vector = NULL;
844
845     if (!PERMcanread) {
846         Printf("%s\r\n", NOACCESS);
847         return;
848     }
849
850     /* Trying to read. */
851     if (GRPcount == 0) {
852         Reply("%s\r\n", ARTnotingroup);
853         return;
854     }
855
856     /* Parse range. */
857     if (!CMDgetrange(ac, av, &range, &DidReply)) {
858         if (DidReply) {
859             return;
860         }
861     }
862
863     OVERcount++;
864     gettimeofday(&stv, NULL);
865     if ((handle = (void *)OVopensearch(GRPcur, range.Low, range.High)) == NULL) {
866         if (av[1] != NULL)
867             Reply("%d %s fields follow\r\n.\r\n", NNTP_OVERVIEW_FOLLOWS_VAL, av[1]);
868         else
869             Reply("%d %d fields follow\r\n.\r\n", NNTP_OVERVIEW_FOLLOWS_VAL, ARTnumber);
870         return;
871     }
872     if (PERMaccessconf->nnrpdoverstats) {
873         gettimeofday(&etv, NULL);
874         OVERtime+=(etv.tv_sec - stv.tv_sec) * 1000;
875         OVERtime+=(etv.tv_usec - stv.tv_usec) / 1000;
876     }
877
878     if (av[1] != NULL)
879         Reply("%d %s fields follow\r\n", NNTP_OVERVIEW_FOLLOWS_VAL, av[1]);
880     else
881         Reply("%d %d fields follow\r\n", NNTP_OVERVIEW_FOLLOWS_VAL, ARTnumber);
882     fflush(stdout);
883     if (PERMaccessconf->nnrpdoverstats)
884         gettimeofday(&stv, NULL);
885
886     /* If OVSTATICSEARCH is true, then the data returned by OVsearch is only
887        valid until the next call to OVsearch.  In this case, we must use
888        SendIOb because it copies the data. */
889     OVctl(OVSTATICSEARCH, &useIOb);
890
891     while (OVsearch(handle, &artnum, &data, &len, &token, NULL)) {
892         if (PERMaccessconf->nnrpdoverstats) {
893             gettimeofday(&etv, NULL);
894             OVERtime+=(etv.tv_sec - stv.tv_sec) * 1000;
895             OVERtime+=(etv.tv_usec - stv.tv_usec) / 1000;
896         }
897         if (len == 0 || (PERMaccessconf->nnrpdcheckart && !ARTinstorebytoken(token))) {
898             if (PERMaccessconf->nnrpdoverstats) {
899                 OVERmiss++;
900                 gettimeofday(&stv, NULL);
901             }
902             continue;
903         }
904         if (PERMaccessconf->nnrpdoverstats) {
905             OVERhit++;
906             OVERsize += len;
907         }
908         vector = overview_split(data, len, NULL, vector);
909         r = overview_getheader(vector, OVERVIEW_MESSAGE_ID, OVextra);
910         cache_add(HashMessageID(r), token);
911         free(r);
912         if (VirtualPathlen > 0 && overhdr_xref != -1) {
913             if ((overhdr_xref + 1) >= vector->count)
914                 continue;
915             p = vector->strings[overhdr_xref] + sizeof("Xref: ") - 1;
916             while ((p < data + len) && *p == ' ')
917                 ++p;
918             q = memchr(p, ' ', data + len - p);
919             if (q == NULL)
920                 continue;
921             if(useIOb) {
922                 SendIOb(data, p - data);
923                 SendIOb(VirtualPath, VirtualPathlen - 1);
924                 SendIOb(q, len - (q - data));
925             } else {
926                 SendIOv(data, p - data);
927                 SendIOv(VirtualPath, VirtualPathlen - 1);
928                 SendIOv(q, len - (q - data));
929             }
930         } else {
931             if(useIOb)
932                 SendIOb(data, len);
933             else
934                 SendIOv(data, len);
935         }
936         if (PERMaccessconf->nnrpdoverstats)
937             gettimeofday(&stv, NULL);
938     }
939
940     if (vector)
941         cvector_free(vector);
942
943     if (PERMaccessconf->nnrpdoverstats) {
944         gettimeofday(&etv, NULL);
945         OVERtime+=(etv.tv_sec - stv.tv_sec) * 1000;
946         OVERtime+=(etv.tv_usec - stv.tv_usec) / 1000;
947     }
948     if(useIOb) {
949         SendIOb(".\r\n", 3);
950         PushIOb();
951     } else {
952         SendIOv(".\r\n", 3);
953         PushIOv();
954     }
955     if (PERMaccessconf->nnrpdoverstats)
956         gettimeofday(&stv, NULL);
957     OVclosesearch(handle);
958     if (PERMaccessconf->nnrpdoverstats) {
959         gettimeofday(&etv, NULL);
960         OVERtime+=(etv.tv_sec - stv.tv_sec) * 1000;
961         OVERtime+=(etv.tv_usec - stv.tv_usec) / 1000;
962     }
963
964 }
965
966 /*
967 **  XHDR and XPAT extensions.  Note that HDR as specified in the new NNTP
968 **  draft works differently than XHDR has historically, so don't just use this
969 **  function to implement it without reviewing the differences.
970 */
971 /* ARGSUSED */
972 void CMDpat(int ac, char *av[])
973 {
974     char                *p;
975     int                 i;
976     ARTRANGE            range;
977     bool                IsLines;
978     bool                DidReply;
979     char                *header;
980     char                *pattern;
981     char                *text;
982     int                 Overview;
983     ARTNUM              artnum;
984     char                buff[SPOOLNAMEBUFF];
985     void                *handle;
986     char                *data;
987     int                 len;
988     TOKEN               token;
989     struct cvector *vector = NULL;
990
991     if (!PERMcanread) {
992         Printf("%s\r\n", NOACCESS);
993         return;
994     }
995
996     header = av[1];
997     IsLines = (strcasecmp(header, "lines") == 0);
998
999     if (ac > 3) /* XPAT */
1000         pattern = Glom(&av[3]);
1001     else
1002         pattern = NULL;
1003
1004     do {
1005         /* Message-ID specified? */
1006         if (ac > 2 && av[2][0] == '<') {
1007             p = av[2];
1008             if (!ARTopenbyid(p, &artnum, false)) {
1009                 Printf("%d No such article.\r\n", NNTP_DONTHAVEIT_VAL);
1010                 break;
1011             }
1012             if (!PERMartok()) {
1013                 ARTclose();
1014                 Printf("%s\r\n", NOACCESS);
1015                 break;
1016             }
1017                 
1018             Printf("%d %s matches follow (ID)\r\n", NNTP_HEAD_FOLLOWS_VAL,
1019                    header);
1020             if ((text = GetHeader(header)) != NULL
1021                 && (!pattern || uwildmat_simple(text, pattern)))
1022                 Printf("%s %s\r\n", p, text);
1023
1024             ARTclose();
1025             Printf(".\r\n");
1026             break;
1027         }
1028
1029         if (GRPcount == 0) {
1030             Reply("%s\r\n", ARTnotingroup);
1031             break;
1032         }
1033
1034         /* Range specified. */
1035         if (!CMDgetrange(ac - 1, av + 1, &range, &DidReply)) {
1036             if (DidReply) {
1037                 break;
1038             }
1039         }
1040
1041         /* In overview? */
1042         Overview = overview_index(header, OVextra);
1043
1044         /* Not in overview, we have to fish headers out from the articles */
1045         if (Overview < 0 ) {
1046             Reply("%d %s matches follow (art)\r\n", NNTP_HEAD_FOLLOWS_VAL,
1047                   header);
1048             for (i = range.Low; i <= range.High && range.High > 0; i++) {
1049                 if (!ARTopen(i))
1050                     continue;
1051                 p = GetHeader(header);
1052                 if (p && (!pattern || uwildmat_simple(p, pattern))) {
1053                     snprintf(buff, sizeof(buff), "%u ", i);
1054                     SendIOb(buff, strlen(buff));
1055                     SendIOb(p, strlen(p));
1056                     SendIOb("\r\n", 2);
1057                 }
1058                 ARTclose();
1059             }
1060             SendIOb(".\r\n", 3);
1061             PushIOb();
1062             break;
1063         }
1064
1065         /* Okay then, we can grab values from overview. */
1066         handle = (void *)OVopensearch(GRPcur, range.Low, range.High);
1067         if (handle == NULL) {
1068             Reply("%d %s no matches follow (NOV)\r\n.\r\n",
1069                   NNTP_HEAD_FOLLOWS_VAL, header);
1070             break;
1071         }       
1072         
1073         Printf("%d %s matches follow (NOV)\r\n", NNTP_HEAD_FOLLOWS_VAL,
1074                header);
1075         while (OVsearch(handle, &artnum, &data, &len, &token, NULL)) {
1076             if (len == 0 || (PERMaccessconf->nnrpdcheckart
1077                 && !ARTinstorebytoken(token)))
1078                 continue;
1079             vector = overview_split(data, len, NULL, vector);
1080             p = overview_getheader(vector, Overview, OVextra);
1081             if (p != NULL) {
1082                 if (PERMaccessconf->virtualhost &&
1083                            Overview == overhdr_xref) {
1084                     p = vhost_xref(p);
1085                     if (p == NULL)
1086                         continue;
1087                 }
1088                 if (!pattern || uwildmat_simple(p, pattern)) {
1089                     snprintf(buff, sizeof(buff), "%lu ", artnum);
1090                     SendIOb(buff, strlen(buff));
1091                     SendIOb(p, strlen(p));
1092                     SendIOb("\r\n", 2);
1093                 }
1094                 free(p);
1095             }
1096         }
1097         SendIOb(".\r\n", 3);
1098         PushIOb();
1099         OVclosesearch(handle);
1100     } while (0);
1101
1102     if (vector)
1103         cvector_free(vector);
1104
1105     if (pattern)
1106         free(pattern);
1107 }