chiark / gitweb /
run debian/rules patch
[inn-innduct.git] / .pc / u_xover_duplicate_reply / 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             Reply("%d data follows\r\n", NNTP_OVERVIEW_FOLLOWS_VAL);
860             Printf(".\r\n");
861             return;
862         }
863     }
864
865     OVERcount++;
866     gettimeofday(&stv, NULL);
867     if ((handle = (void *)OVopensearch(GRPcur, range.Low, range.High)) == NULL) {
868         if (av[1] != NULL)
869             Reply("%d %s fields follow\r\n.\r\n", NNTP_OVERVIEW_FOLLOWS_VAL, av[1]);
870         else
871             Reply("%d %d fields follow\r\n.\r\n", NNTP_OVERVIEW_FOLLOWS_VAL, ARTnumber);
872         return;
873     }
874     if (PERMaccessconf->nnrpdoverstats) {
875         gettimeofday(&etv, NULL);
876         OVERtime+=(etv.tv_sec - stv.tv_sec) * 1000;
877         OVERtime+=(etv.tv_usec - stv.tv_usec) / 1000;
878     }
879
880     if (av[1] != NULL)
881         Reply("%d %s fields follow\r\n", NNTP_OVERVIEW_FOLLOWS_VAL, av[1]);
882     else
883         Reply("%d %d fields follow\r\n", NNTP_OVERVIEW_FOLLOWS_VAL, ARTnumber);
884     fflush(stdout);
885     if (PERMaccessconf->nnrpdoverstats)
886         gettimeofday(&stv, NULL);
887
888     /* If OVSTATICSEARCH is true, then the data returned by OVsearch is only
889        valid until the next call to OVsearch.  In this case, we must use
890        SendIOb because it copies the data. */
891     OVctl(OVSTATICSEARCH, &useIOb);
892
893     while (OVsearch(handle, &artnum, &data, &len, &token, NULL)) {
894         if (PERMaccessconf->nnrpdoverstats) {
895             gettimeofday(&etv, NULL);
896             OVERtime+=(etv.tv_sec - stv.tv_sec) * 1000;
897             OVERtime+=(etv.tv_usec - stv.tv_usec) / 1000;
898         }
899         if (len == 0 || (PERMaccessconf->nnrpdcheckart && !ARTinstorebytoken(token))) {
900             if (PERMaccessconf->nnrpdoverstats) {
901                 OVERmiss++;
902                 gettimeofday(&stv, NULL);
903             }
904             continue;
905         }
906         if (PERMaccessconf->nnrpdoverstats) {
907             OVERhit++;
908             OVERsize += len;
909         }
910         vector = overview_split(data, len, NULL, vector);
911         r = overview_getheader(vector, OVERVIEW_MESSAGE_ID, OVextra);
912         cache_add(HashMessageID(r), token);
913         free(r);
914         if (VirtualPathlen > 0 && overhdr_xref != -1) {
915             if ((overhdr_xref + 1) >= vector->count)
916                 continue;
917             p = vector->strings[overhdr_xref] + sizeof("Xref: ") - 1;
918             while ((p < data + len) && *p == ' ')
919                 ++p;
920             q = memchr(p, ' ', data + len - p);
921             if (q == NULL)
922                 continue;
923             if(useIOb) {
924                 SendIOb(data, p - data);
925                 SendIOb(VirtualPath, VirtualPathlen - 1);
926                 SendIOb(q, len - (q - data));
927             } else {
928                 SendIOv(data, p - data);
929                 SendIOv(VirtualPath, VirtualPathlen - 1);
930                 SendIOv(q, len - (q - data));
931             }
932         } else {
933             if(useIOb)
934                 SendIOb(data, len);
935             else
936                 SendIOv(data, len);
937         }
938         if (PERMaccessconf->nnrpdoverstats)
939             gettimeofday(&stv, NULL);
940     }
941
942     if (vector)
943         cvector_free(vector);
944
945     if (PERMaccessconf->nnrpdoverstats) {
946         gettimeofday(&etv, NULL);
947         OVERtime+=(etv.tv_sec - stv.tv_sec) * 1000;
948         OVERtime+=(etv.tv_usec - stv.tv_usec) / 1000;
949     }
950     if(useIOb) {
951         SendIOb(".\r\n", 3);
952         PushIOb();
953     } else {
954         SendIOv(".\r\n", 3);
955         PushIOv();
956     }
957     if (PERMaccessconf->nnrpdoverstats)
958         gettimeofday(&stv, NULL);
959     OVclosesearch(handle);
960     if (PERMaccessconf->nnrpdoverstats) {
961         gettimeofday(&etv, NULL);
962         OVERtime+=(etv.tv_sec - stv.tv_sec) * 1000;
963         OVERtime+=(etv.tv_usec - stv.tv_usec) / 1000;
964     }
965
966 }
967
968 /*
969 **  XHDR and XPAT extensions.  Note that HDR as specified in the new NNTP
970 **  draft works differently than XHDR has historically, so don't just use this
971 **  function to implement it without reviewing the differences.
972 */
973 /* ARGSUSED */
974 void CMDpat(int ac, char *av[])
975 {
976     char                *p;
977     int                 i;
978     ARTRANGE            range;
979     bool                IsLines;
980     bool                DidReply;
981     char                *header;
982     char                *pattern;
983     char                *text;
984     int                 Overview;
985     ARTNUM              artnum;
986     char                buff[SPOOLNAMEBUFF];
987     void                *handle;
988     char                *data;
989     int                 len;
990     TOKEN               token;
991     struct cvector *vector = NULL;
992
993     if (!PERMcanread) {
994         Printf("%s\r\n", NOACCESS);
995         return;
996     }
997
998     header = av[1];
999     IsLines = (strcasecmp(header, "lines") == 0);
1000
1001     if (ac > 3) /* XPAT */
1002         pattern = Glom(&av[3]);
1003     else
1004         pattern = NULL;
1005
1006     do {
1007         /* Message-ID specified? */
1008         if (ac > 2 && av[2][0] == '<') {
1009             p = av[2];
1010             if (!ARTopenbyid(p, &artnum, false)) {
1011                 Printf("%d No such article.\r\n", NNTP_DONTHAVEIT_VAL);
1012                 break;
1013             }
1014             if (!PERMartok()) {
1015                 ARTclose();
1016                 Printf("%s\r\n", NOACCESS);
1017                 break;
1018             }
1019                 
1020             Printf("%d %s matches follow (ID)\r\n", NNTP_HEAD_FOLLOWS_VAL,
1021                    header);
1022             if ((text = GetHeader(header)) != NULL
1023                 && (!pattern || uwildmat_simple(text, pattern)))
1024                 Printf("%s %s\r\n", p, text);
1025
1026             ARTclose();
1027             Printf(".\r\n");
1028             break;
1029         }
1030
1031         if (GRPcount == 0) {
1032             Reply("%s\r\n", ARTnotingroup);
1033             break;
1034         }
1035
1036         /* Range specified. */
1037         if (!CMDgetrange(ac - 1, av + 1, &range, &DidReply)) {
1038             if (!DidReply) {
1039                 Reply("%d %s no matches follow (range)\r\n",
1040                       NNTP_HEAD_FOLLOWS_VAL, header ? header : "\"\"");
1041                 Printf(".\r\n");
1042                 break;
1043             }
1044         }
1045
1046         /* In overview? */
1047         Overview = overview_index(header, OVextra);
1048
1049         /* Not in overview, we have to fish headers out from the articles */
1050         if (Overview < 0 ) {
1051             Reply("%d %s matches follow (art)\r\n", NNTP_HEAD_FOLLOWS_VAL,
1052                   header);
1053             for (i = range.Low; i <= range.High && range.High > 0; i++) {
1054                 if (!ARTopen(i))
1055                     continue;
1056                 p = GetHeader(header);
1057                 if (p && (!pattern || uwildmat_simple(p, pattern))) {
1058                     snprintf(buff, sizeof(buff), "%u ", i);
1059                     SendIOb(buff, strlen(buff));
1060                     SendIOb(p, strlen(p));
1061                     SendIOb("\r\n", 2);
1062                 }
1063                 ARTclose();
1064             }
1065             SendIOb(".\r\n", 3);
1066             PushIOb();
1067             break;
1068         }
1069
1070         /* Okay then, we can grab values from overview. */
1071         handle = (void *)OVopensearch(GRPcur, range.Low, range.High);
1072         if (handle == NULL) {
1073             Reply("%d %s no matches follow (NOV)\r\n.\r\n",
1074                   NNTP_HEAD_FOLLOWS_VAL, header);
1075             break;
1076         }       
1077         
1078         Printf("%d %s matches follow (NOV)\r\n", NNTP_HEAD_FOLLOWS_VAL,
1079                header);
1080         while (OVsearch(handle, &artnum, &data, &len, &token, NULL)) {
1081             if (len == 0 || (PERMaccessconf->nnrpdcheckart
1082                 && !ARTinstorebytoken(token)))
1083                 continue;
1084             vector = overview_split(data, len, NULL, vector);
1085             p = overview_getheader(vector, Overview, OVextra);
1086             if (p != NULL) {
1087                 if (PERMaccessconf->virtualhost &&
1088                            Overview == overhdr_xref) {
1089                     p = vhost_xref(p);
1090                     if (p == NULL)
1091                         continue;
1092                 }
1093                 if (!pattern || uwildmat_simple(p, pattern)) {
1094                     snprintf(buff, sizeof(buff), "%lu ", artnum);
1095                     SendIOb(buff, strlen(buff));
1096                     SendIOb(p, strlen(p));
1097                     SendIOb("\r\n", 2);
1098                 }
1099                 free(p);
1100             }
1101         }
1102         SendIOb(".\r\n", 3);
1103         PushIOb();
1104         OVclosesearch(handle);
1105     } while (0);
1106
1107     if (vector)
1108         cvector_free(vector);
1109
1110     if (pattern)
1111         free(pattern);
1112 }