1 /*  $Id: article.c 10149 2017-06-05 12:24:58Z iulius $
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 #include <ctype.h>
14 
15 #include "inn/innconf.h"
16 #include "inn/messages.h"
17 #include "inn/wire.h"
18 #include "nnrpd.h"
19 #include "inn/ov.h"
20 #include "tls.h"
21 #include "cache.h"
22 
23 extern bool laxmid;
24 #ifdef HAVE_OPENSSL
25 extern SSL *tls_conn;
26 #endif
27 
28 /*
29 **  Data structures for use in ARTICLE/HEAD/BODY/STAT common code.
30 */
31 typedef enum _SENDTYPE {
32     STarticle,
33     SThead,
34     STbody,
35     STstat
36 } SENDTYPE;
37 
38 typedef struct _SENDDATA {
39     SENDTYPE    Type;
40     int         ReplyCode;
41     const char *Item;
42 } SENDDATA;
43 
44 static ARTHANDLE        *ARThandle = NULL;
45 static SENDDATA		SENDbody = {
46     STbody,	NNTP_OK_BODY,		"body"
47 };
48 static SENDDATA		SENDarticle = {
49     STarticle,	NNTP_OK_ARTICLE,	"article"
50 };
51 static SENDDATA		SENDstat = {
52     STstat,     NNTP_OK_STAT,           "status"
53 };
54 static SENDDATA		SENDhead = {
55     SThead,	NNTP_OK_HEAD,		"head"
56 };
57 
58 static struct iovec	iov[IOV_MAX > 1024 ? 1024 : IOV_MAX];
59 static int		queued_iov = 0;
60 
61 static void
PushIOvHelper(struct iovec * vec,int * countp)62 PushIOvHelper(struct iovec* vec, int* countp)
63 {
64     int result = 0;
65 
66     TMRstart(TMR_NNTPWRITE);
67 
68 #if defined(HAVE_ZLIB)
69     if (compression_layer_on) {
70         int i;
71 
72         for (i = 0; i < *countp; i++) {
73             if (i+1 == *countp) {
74                 /* Time to flush the compressed output stream. */
75                 zstream_flush_needed = true;
76             }
77             write_buffer(vec[i].iov_base, vec[i].iov_len);
78         }
79 
80         *countp = 0;
81         return;
82     }
83 #endif /* HAVE_ZLIB */
84 
85 #ifdef HAVE_SASL
86     if (sasl_conn && sasl_ssf) {
87 	int i;
88 
89 	for (i = 0; i < *countp; i++) {
90 	    write_buffer(vec[i].iov_base, vec[i].iov_len);
91 	}
92     } else {
93 #endif /* HAVE_SASL */
94 
95 #ifdef HAVE_OPENSSL
96 	if (tls_conn) {
97 Again:
98 	    result = SSL_writev(tls_conn, vec, *countp);
99 	    switch (SSL_get_error(tls_conn, result)) {
100 	    case SSL_ERROR_NONE:
101 	    case SSL_ERROR_SYSCALL:
102 		break;
103 	    case SSL_ERROR_WANT_WRITE:
104 		goto Again;
105 		break;
106 	    case SSL_ERROR_SSL:
107 		SSL_shutdown(tls_conn);
108 		tls_conn = NULL;
109 		errno = ECONNRESET;
110 		break;
111 	    case SSL_ERROR_ZERO_RETURN:
112 		break;
113 	    }
114 	} else
115 #endif /* HAVE_OPENSSL */
116 	    result = xwritev(STDOUT_FILENO, vec, *countp);
117 
118 #ifdef HAVE_SASL
119     }
120 #endif
121 
122     TMRstop(TMR_NNTPWRITE);
123 
124     if (result == -1) {
125 	/* We can't recover, since we can't resynchronise with our
126 	 * peer. */
127 	ExitWithStats(1, true);
128     }
129     *countp = 0;
130 }
131 
132 static void
PushIOvRateLimited(void)133 PushIOvRateLimited(void)
134 {
135     double              start, end, elapsed, target;
136     struct iovec        newiov[IOV_MAX > 1024 ? 1024 : IOV_MAX];
137     int                 newiov_len;
138     int                 sentiov;
139     int                 i;
140     int                 bytesfound;
141     int                 chunkbittenoff;
142     struct timeval      waittime;
143 
144     while (queued_iov) {
145 	bytesfound = newiov_len = 0;
146 	sentiov = 0;
147 	for (i = 0; (i < queued_iov) && (bytesfound < MaxBytesPerSecond); i++) {
148 	    if ((signed)iov[i].iov_len + bytesfound > MaxBytesPerSecond) {
149 		chunkbittenoff = MaxBytesPerSecond - bytesfound;
150 		newiov[newiov_len].iov_base = iov[i].iov_base;
151 		newiov[newiov_len++].iov_len = chunkbittenoff;
152 		iov[i].iov_base = (char *)iov[i].iov_base + chunkbittenoff;
153 		iov[i].iov_len -= chunkbittenoff;
154 		bytesfound += chunkbittenoff;
155 	    } else {
156 		newiov[newiov_len++] = iov[i];
157 		sentiov++;
158 		bytesfound += iov[i].iov_len;
159 	    }
160 	}
161 	assert(sentiov <= queued_iov);
162         start = TMRnow_double();
163 	PushIOvHelper(newiov, &newiov_len);
164         end = TMRnow_double();
165         target = (double) bytesfound / MaxBytesPerSecond;
166         elapsed = end - start;
167         if (elapsed < 1 && elapsed < target) {
168             waittime.tv_sec = 0;
169             waittime.tv_usec = (target - elapsed) * 1e6;
170             start = TMRnow_double();
171 	    if (select(0, NULL, NULL, NULL, &waittime) != 0)
172                 syswarn("%s: select in PushIOvRateLimit failed", Client.host);
173             end = TMRnow_double();
174             IDLEtime += end - start;
175 	}
176 	memmove(iov, &iov[sentiov], (queued_iov - sentiov) * sizeof(struct iovec));
177 	queued_iov -= sentiov;
178     }
179 }
180 
181 static void
PushIOv(void)182 PushIOv(void)
183 {
184     TMRstart(TMR_NNTPWRITE);
185     fflush(stdout);
186     TMRstop(TMR_NNTPWRITE);
187     if (MaxBytesPerSecond != 0)
188 	PushIOvRateLimited();
189     else
190 	PushIOvHelper(iov, &queued_iov);
191 }
192 
193 static void
SendIOv(const char * p,int len)194 SendIOv(const char *p, int len)
195 {
196     char                *q;
197 
198     if (queued_iov) {
199 	q = (char *)iov[queued_iov - 1].iov_base + iov[queued_iov - 1].iov_len;
200 	if (p == q) {
201 	    iov[queued_iov - 1].iov_len += len;
202 	    return;
203 	}
204     }
205     iov[queued_iov].iov_base = (char*)p;
206     iov[queued_iov++].iov_len = len;
207     if (queued_iov == IOV_MAX)
208 	PushIOv();
209 }
210 
211 static char		*_IO_buffer_ = NULL;
212 static int		highwater = 0;
213 
214 static void
PushIOb(void)215 PushIOb(void)
216 {
217 #if defined(HAVE_ZLIB)
218     /* Last line of a multi-line data block response.
219      * Time to flush the compressed output stream. */
220     zstream_flush_needed = true;
221 #endif /* HAVE_ZLIB */
222 
223     write_buffer(_IO_buffer_, highwater);
224     highwater = 0;
225 }
226 
227 static void
SendIOb(const char * p,int len)228 SendIOb(const char *p, int len)
229 {
230     int tocopy;
231 
232     if (_IO_buffer_ == NULL)
233         _IO_buffer_ = xmalloc(BIG_BUFFER);
234 
235     while (len > 0) {
236         tocopy = (len > (BIG_BUFFER - highwater)) ? (BIG_BUFFER - highwater) : len;
237         memcpy(&_IO_buffer_[highwater], p, tocopy);
238         p += tocopy;
239         highwater += tocopy;
240         len -= tocopy;
241         if (highwater == BIG_BUFFER)
242             PushIOb();
243     }
244 }
245 
246 
247 /*
248 **  If we have an article open, close it.
249 */
250 void
ARTclose(void)251 ARTclose(void)
252 {
253     if (ARThandle) {
254 	SMfreearticle(ARThandle);
255 	ARThandle = NULL;
256     }
257 }
258 
259 bool
ARTinstorebytoken(TOKEN token)260 ARTinstorebytoken(TOKEN token)
261 {
262     ARTHANDLE *art;
263     struct timeval	stv, etv;
264 
265     if (PERMaccessconf->nnrpdoverstats) {
266 	gettimeofday(&stv, NULL);
267     }
268     art = SMretrieve(token, RETR_STAT);
269     /* XXX This isn't really overstats, is it? */
270     if (PERMaccessconf->nnrpdoverstats) {
271 	gettimeofday(&etv, NULL);
272 	OVERartcheck+=(etv.tv_sec - stv.tv_sec) * 1000;
273 	OVERartcheck+=(etv.tv_usec - stv.tv_usec) / 1000;
274     }
275     if (art) {
276 	SMfreearticle(art);
277 	return true;
278     }
279     return false;
280 }
281 
282 /*
283 **  If the article name is valid, open it and stuff in the ID.
284 */
285 static bool
ARTopen(ARTNUM artnum)286 ARTopen(ARTNUM artnum)
287 {
288     static ARTNUM	save_artnum;
289     TOKEN		token;
290 
291     /* Re-use article if it's the same one. */
292     if (save_artnum == artnum) {
293 	if (ARThandle)
294 	    return true;
295     }
296     ARTclose();
297 
298     if (!OVgetartinfo(GRPcur, artnum, &token))
299 	return false;
300 
301     TMRstart(TMR_READART);
302     ARThandle = SMretrieve(token, RETR_ALL);
303     TMRstop(TMR_READART);
304     if (ARThandle == NULL) {
305 	return false;
306     }
307 
308     save_artnum = artnum;
309     return true;
310 }
311 
312 
313 /*
314 **  Open the article for a given message-ID.
315 */
316 static bool
ARTopenbyid(char * msg_id,ARTNUM * ap,bool final)317 ARTopenbyid(char *msg_id, ARTNUM *ap, bool final)
318 {
319     TOKEN token;
320 
321     *ap = 0;
322     token = cache_get(HashMessageID(msg_id), final);
323     if (token.type == TOKEN_EMPTY) {
324 	if (History == NULL) {
325 	    time_t statinterval;
326 
327 	    /* Do lazy opens of the history file:  lots of clients
328 	     * will never ask for anything by message-ID, so put off
329 	     * doing the work until we have to. */
330 	    History = HISopen(HISTORY, innconf->hismethod, HIS_RDONLY);
331 	    if (!History) {
332 		syslog(L_NOTICE, "can't initialize history");
333 		Reply("%d NNTP server unavailable; try later\r\n",
334 		      NNTP_FAIL_TERMINATING);
335 		ExitWithStats(1, true);
336 	    }
337 	    statinterval = 30;
338 	    HISctl(History, HISCTLS_STATINTERVAL, &statinterval);
339 	}
340 	if (!HISlookup(History, msg_id, NULL, NULL, NULL, &token))
341 	    return false;
342     }
343     if (token.type == TOKEN_EMPTY)
344 	return false;
345     TMRstart(TMR_READART);
346     ARThandle = SMretrieve(token, RETR_ALL);
347     TMRstop(TMR_READART);
348     if (ARThandle == NULL) {
349 	return false;
350     }
351 
352     return true;
353 }
354 
355 /*
356 **  Send a (part of) a file to stdout, doing newline and dot conversion.
357 */
358 static void
ARTsendmmap(SENDTYPE what)359 ARTsendmmap(SENDTYPE what)
360 {
361     const char		*p, *q, *r;
362     const char		*s, *path, *xref, *endofpath;
363     char		lastchar;
364 
365     ARTcount++;
366     GRParticles++;
367     lastchar = -1;
368 
369     /* Get the headers and detect if wire format. */
370     if (what == STarticle) {
371 	q = ARThandle->data;
372 	p = ARThandle->data + ARThandle->len;
373      } else {
374 	for (q = p = ARThandle->data; p < (ARThandle->data + ARThandle->len); p++) {
375 	    if (*p == '\r')
376 		continue;
377 	    if (*p == '\n') {
378 		if (lastchar == '\n') {
379 		    if (what == SThead) {
380 			if (*(p-1) == '\r')
381 			    p--;
382 			break;
383 		    } else {
384 			q = p + 1;
385 			p = ARThandle->data + ARThandle->len;
386 			break;
387 		    }
388 		}
389 	    }
390 	    lastchar = *p;
391 	}
392     }
393 
394     /* q points to the start of the article buffer, p to the end of it. */
395     if (VirtualPathlen > 0 && (what != STbody)) {
396         path = wire_findheader(ARThandle->data, ARThandle->len, "Path", true);
397         if (path == NULL) {
398 	    SendIOv(".\r\n", 3);
399 	    ARTgetsize += 3;
400 	    PushIOv();
401 	    ARTget++;
402 	    return;
403 	} else {
404             xref = wire_findheader(ARThandle->data, ARThandle->len, "Xref", true);
405             if (xref == NULL) {
406                 SendIOv(".\r\n", 3);
407                 ARTgetsize += 3;
408                 PushIOv();
409                 ARTget++;
410                 return;
411             }
412         }
413         endofpath = wire_endheader(path, ARThandle->data + ARThandle->len - 1);
414         if (endofpath == NULL) {
415 	    SendIOv(".\r\n", 3);
416 	    ARTgetsize += 3;
417 	    PushIOv();
418 	    ARTget++;
419 	    return;
420 	}
421 	if ((r = memchr(xref, ' ', p - xref)) == NULL || r == p) {
422 	    SendIOv(".\r\n", 3);
423 	    ARTgetsize += 3;
424 	    PushIOv();
425 	    ARTget++;
426 	    return;
427 	}
428 	/* r points to the first space in the Xref: header. */
429 
430 	for (s = path, lastchar = '\0';
431 	    s + VirtualPathlen + 1 < endofpath;
432 	    lastchar = *s++) {
433 	    if ((lastchar != '\0' && lastchar != '!') || *s != *VirtualPath ||
434 		strncasecmp(s, VirtualPath, VirtualPathlen - 1) != 0)
435 		continue;
436 	    if (*(s + VirtualPathlen - 1) != '\0' &&
437 		*(s + VirtualPathlen - 1) != '!')
438 		continue;
439 	    break;
440 	}
441 	if (s + VirtualPathlen + 1 < endofpath) {
442 	    if (xref > path) {
443 	        SendIOv(q, path - q);
444 	        SendIOv(s, xref - s);
445 	        SendIOv(VirtualPath, VirtualPathlen - 1);
446 	        SendIOv(r, p - r);
447 	    } else {
448 	        SendIOv(q, xref - q);
449 	        SendIOv(VirtualPath, VirtualPathlen - 1);
450 	        SendIOv(r, path - r);
451 	        SendIOv(s, p - s);
452 	    }
453 	} else {
454             /* Double the '!' (thus, adding one '!') in Path: header. */
455 	    if (xref > path) {
456 	        SendIOv(q, path - q);
457 	        SendIOv(VirtualPath, VirtualPathlen);
458                 SendIOv("!", 1);
459 	        SendIOv(path, xref - path);
460 	        SendIOv(VirtualPath, VirtualPathlen - 1);
461 	        SendIOv(r, p - r);
462 	    } else {
463 	        SendIOv(q, xref - q);
464 	        SendIOv(VirtualPath, VirtualPathlen - 1);
465 	        SendIOv(r, path - r);
466 	        SendIOv(VirtualPath, VirtualPathlen);
467                 SendIOv("!", 1);
468 	        SendIOv(path, p - path);
469 	    }
470 	}
471     } else
472 	SendIOv(q, p - q);
473     ARTgetsize += p - q;
474     if (what == SThead) {
475 	SendIOv(".\r\n", 3);
476 	ARTgetsize += 3;
477     } else if (memcmp((ARThandle->data + ARThandle->len - 5), "\r\n.\r\n", 5)) {
478 	if (memcmp((ARThandle->data + ARThandle->len - 2), "\r\n", 2)) {
479 	    SendIOv("\r\n.\r\n", 5);
480 	    ARTgetsize += 5;
481 	} else {
482 	    SendIOv(".\r\n", 3);
483 	    ARTgetsize += 3;
484 	}
485     }
486     PushIOv();
487 
488     ARTget++;
489 }
490 
491 /*
492 **  Return the header from the specified file, or NULL if not found.
493 */
494 char *
GetHeader(const char * header,bool stripspaces)495 GetHeader(const char *header, bool stripspaces)
496 {
497     const char		*p, *q, *r, *s, *t;
498     char		*w, *wnew, prevchar;
499     /* Bogus value here to make sure that it isn't initialized to \n. */
500     char		lastchar = ' ';
501     const char		*limit;
502     const char		*cmplimit;
503     static char		*retval = NULL;
504     static int		retlen = 0;
505     int			headerlen;
506     bool		pathheader = false;
507     bool		xrefheader = false;
508 
509     limit = ARThandle->data + ARThandle->len;
510     cmplimit = ARThandle->data + ARThandle->len - strlen(header) - 1;
511     for (p = ARThandle->data; p < cmplimit; p++) {
512 	if (*p == '\r')
513 	    continue;
514 	if ((lastchar == '\n') && (*p == '\n')) {
515 	    return NULL;
516 	}
517 	if ((lastchar == '\n') || (p == ARThandle->data)) {
518 	    headerlen = strlen(header);
519 	    if (strncasecmp(p, header, headerlen) == 0 && p[headerlen] == ':' &&
520                 ISWHITE(p[headerlen+1])) {
521                 p += headerlen + 2;
522                 if (stripspaces) {
523                     for (; (p < limit) && ISWHITE(*p) ; p++);
524                 }
525 		for (q = p; q < limit; q++)
526 		    if ((*q == '\r') || (*q == '\n')) {
527 			/* Check for continuation header lines. */
528 			t = q + 1;
529 			if (t < limit) {
530 			    if ((*q == '\r' && *t == '\n')) {
531 				t++;
532 				if (t == limit)
533 				    break;
534 			    }
535 			    if ((*t == '\t' || *t == ' ')) {
536 				for (; (t < limit) && ISWHITE(*t) ; t++);
537 				q = t;
538 			    } else {
539 				break;
540 			    }
541 			} else {
542 			    break;
543 			}
544 		    }
545 		if (q == limit)
546 		    return NULL;
547 		if (strncasecmp("Path", header, headerlen) == 0)
548 		    pathheader = true;
549 		else if (strncasecmp("Xref", header, headerlen) == 0)
550 		    xrefheader = true;
551 		if (retval == NULL) {
552                     /* Possibly add '!' (a second one) at the end of the virtual path.
553                      * So it is +2 because of '\0'. */
554 		    retlen = q - p + VirtualPathlen + 2;
555 		    retval = xmalloc(retlen);
556 		} else {
557 		    if ((q - p + VirtualPathlen + 2) > retlen) {
558 			retlen = q - p + VirtualPathlen + 2;
559                         retval = xrealloc(retval, retlen);
560 		    }
561 		}
562 		if (pathheader && (VirtualPathlen > 0)) {
563                     const char *endofpath;
564                     const char *endofarticle;
565 
566                     endofarticle = ARThandle->data + ARThandle->len - 1;
567                     endofpath = wire_endheader(p, endofarticle);
568                     if (endofpath == NULL)
569                         return NULL;
570 		    for (s = p, prevchar = '\0';
571 			s + VirtualPathlen + 1 < endofpath;
572 			prevchar = *s++) {
573 			if ((prevchar != '\0' && prevchar != '!') ||
574 			    *s != *VirtualPath ||
575 			    strncasecmp(s, VirtualPath, VirtualPathlen - 1) != 0)
576 			    continue;
577 			if (*(s + VirtualPathlen - 1) != '\0' &&
578 			    *(s + VirtualPathlen - 1) != '!')
579 			    continue;
580 			break;
581 		    }
582 		    if (s + VirtualPathlen + 1 < endofpath) {
583 			memcpy(retval, s, q - s);
584 			*(retval + (int)(q - s)) = '\0';
585 		    } else {
586 			memcpy(retval, VirtualPath, VirtualPathlen);
587                         *(retval + VirtualPathlen) = '!';
588 			memcpy(retval + VirtualPathlen + 1, p, q - p);
589 			*(retval + (int)(q - p) + VirtualPathlen + 1) = '\0';
590 		    }
591 		} else if (xrefheader && (VirtualPathlen > 0)) {
592 		    if ((r = memchr(p, ' ', q - p)) == NULL)
593 			return NULL;
594 		    for (; (r < q) && isspace((unsigned char) *r) ; r++);
595 		    if (r == q)
596 			return NULL;
597                     /* Copy the virtual path without its final '!'. */
598 		    memcpy(retval, VirtualPath, VirtualPathlen - 1);
599 		    memcpy(retval + VirtualPathlen - 1, r - 1, q - r + 1);
600 		    *(retval + (int)(q - r) + VirtualPathlen) = '\0';
601 		} else {
602 		    memcpy(retval, p, q - p);
603 		    *(retval + (int)(q - p)) = '\0';
604 		}
605 		for (w = retval, wnew = retval; *w; w++) {
606                     if (*w == '\r' && w[1] == '\n') {
607                         w++;
608                         continue;
609                     }
610 		    if (*w == '\0' || *w == '\t' || *w == '\r' || *w == '\n') {
611 			*wnew = ' ';
612                     } else {
613                         *wnew = *w;
614                     }
615                     wnew++;
616                 }
617                 *wnew = '\0';
618 		return retval;
619 	    }
620 	}
621 	lastchar = *p;
622     }
623     return NULL;
624 }
625 
626 /*
627 **  Fetch part or all of an article and send it to the client.
628 */
629 void
CMDfetch(int ac,char * av[])630 CMDfetch(int ac, char *av[])
631 {
632     char		buff[SMBUF];
633     SENDDATA		*what;
634     bool                mid, ok;
635     ARTNUM		art;
636     char		*msgid;
637     ARTNUM		tart;
638     bool                final = false;
639 
640     mid = (ac > 1 && IsValidMessageID(av[1], true, laxmid));
641 
642     /* Check the syntax of the arguments first. */
643     if (ac > 1 && !IsValidArticleNumber(av[1])) {
644         /* It is better to check for a number before a message-ID because
645          * '<' could have been forgotten and the error message should then
646          * report a syntax error in the message-ID. */
647         if (isdigit((unsigned char) av[1][0])) {
648             Reply("%d Syntax error in article number\r\n", NNTP_ERR_SYNTAX);
649             return;
650         } else if (!mid) {
651             Reply("%d Syntax error in message-ID\r\n", NNTP_ERR_SYNTAX);
652             return;
653         }
654     }
655 
656     /* Find what to send; get permissions. */
657     ok = PERMcanread;
658     switch (*av[0]) {
659     default:
660 	what = &SENDbody;
661 	final = true;
662 	break;
663     case 'a': case 'A':
664 	what = &SENDarticle;
665 	final = true;
666 	break;
667     case 's': case 'S':
668 	what = &SENDstat;
669 	break;
670     case 'h': case 'H':
671 	what = &SENDhead;
672 	/* Poster might do a HEAD command to verify the article. */
673 	ok = PERMcanread || PERMcanpost;
674 	break;
675     }
676 
677     /* Trying to read. */
678     if (GRPcount == 0 && !mid) {
679         Reply("%d Not in a newsgroup\r\n", NNTP_FAIL_NO_GROUP);
680         return;
681     }
682 
683     /* Check authorizations.  If an article number is requested
684      * (not a message-ID), we check whether the group is still readable. */
685     if (!ok || (!mid && PERMgroupmadeinvalid)) {
686 	Reply("%d Read access denied\r\n",
687               PERMcanauthenticate ? NNTP_FAIL_AUTH_NEEDED : NNTP_ERR_ACCESS);
688 	return;
689     }
690 
691     /* Requesting by message-ID? */
692     if (mid) {
693 	if (!ARTopenbyid(av[1], &art, final)) {
694 	    Reply("%d No such article\r\n", NNTP_FAIL_MSGID_NOTFOUND);
695 	    return;
696 	}
697 	if (!PERMartok()) {
698 	    ARTclose();
699 	    Reply("%d Read access denied for this article\r\n",
700                   PERMcanauthenticate ? NNTP_FAIL_AUTH_NEEDED : NNTP_ERR_ACCESS);
701 	    return;
702 	}
703 	Reply("%d %lu %s %s\r\n", what->ReplyCode, art, av[1], what->Item);
704 	if (what->Type != STstat) {
705 	    ARTsendmmap(what->Type);
706 	}
707 	ARTclose();
708 	return;
709     }
710 
711     /* Default is to get current article, or specified article. */
712     if (ac == 1) {
713         if (ARTnumber < ARTlow || ARTnumber > ARThigh) {
714             Reply("%d Current article number %lu is invalid\r\n",
715                   NNTP_FAIL_ARTNUM_INVALID, ARTnumber);
716             return;
717         }
718         snprintf(buff, sizeof(buff), "%lu", ARTnumber);
719         tart = ARTnumber;
720     } else {
721         /* We have already checked that the article number is valid. */
722         strlcpy(buff, av[1], sizeof(buff));
723         tart = (ARTNUM)atol(buff);
724     }
725 
726     /* Open the article and send the reply. */
727     if (!ARTopen(tart)) {
728         Reply("%d No such article number %lu\r\n", NNTP_FAIL_ARTNUM_NOTFOUND, tart);
729         return;
730     }
731     if ((msgid = GetHeader("Message-ID", true)) == NULL) {
732         ARTclose();
733         Reply("%d No such article number %lu\r\n", NNTP_FAIL_ARTNUM_NOTFOUND, tart);
734         return;
735     }
736     if (ac > 1)
737         ARTnumber = tart;
738 
739     /* A message-ID does not have more than 250 octets. */
740     Reply("%d %s %.250s %s\r\n", what->ReplyCode, buff, msgid, what->Item);
741     if (what->Type != STstat)
742         ARTsendmmap(what->Type);
743     ARTclose();
744 }
745 
746 
747 /*
748 **  Go to the next or last (really previous) article in the group.
749 */
750 void
CMDnextlast(int ac UNUSED,char * av[])751 CMDnextlast(int ac UNUSED, char *av[])
752 {
753     char *msgid;
754     int	save, delta, errcode;
755     bool next;
756     const char *message;
757 
758     /* Trying to read. */
759     if (GRPcount == 0) {
760         Reply("%d Not in a newsgroup\r\n", NNTP_FAIL_NO_GROUP);
761         return;
762     }
763 
764     /* No syntax to check.  Only check authorizations. */
765     if (!PERMcanread || PERMgroupmadeinvalid) {
766 	Reply("%d Read access denied\r\n",
767               PERMcanauthenticate ? NNTP_FAIL_AUTH_NEEDED : NNTP_ERR_ACCESS);
768 	return;
769     }
770 
771     if (ARTnumber < ARTlow || ARTnumber > ARThigh) {
772         Reply("%d Current article number %lu is invalid\r\n",
773               NNTP_FAIL_ARTNUM_INVALID, ARTnumber);
774         return;
775     }
776 
777     /* NEXT? */
778     next = (av[0][0] == 'n' || av[0][0] == 'N');
779     if (next) {
780 	delta = 1;
781 	errcode = NNTP_FAIL_NEXT;
782 	message = "next";
783     }
784     else {
785 	delta = -1;
786 	errcode = NNTP_FAIL_PREV;
787 	message = "previous";
788     }
789 
790     save = ARTnumber;
791     msgid = NULL;
792     do {
793         ARTnumber += delta;
794         if (ARTnumber < ARTlow || ARTnumber > ARThigh) {
795             Reply("%d No %s article to retrieve\r\n", errcode, message);
796             ARTnumber = save;
797             return;
798         }
799         if (!ARTopen(ARTnumber))
800             continue;
801         msgid = GetHeader("Message-ID", true);
802         ARTclose();
803     } while (msgid == NULL);
804 
805     Reply("%d %lu %s Article retrieved; request text separately\r\n",
806 	   NNTP_OK_STAT, ARTnumber, msgid);
807 }
808 
809 
810 /*
811 **  Parse a range (in av[1]) which may be any of the following:
812 **    - An article number.
813 **    - An article number followed by a dash to indicate all following.
814 **    - An article number followed by a dash followed by another article
815 **      number.
816 **
817 **  In the last case, if the second number is less than the first number,
818 **  then the range contains no articles.
819 **
820 **  In addition to RFC 3977, we also accept:
821 **    - A dash followed by an article number to indicate all previous.
822 **    - A dash for everything.
823 **
824 **  ac is the number of arguments in the command:
825 **    LISTGROUP news.software.nntp 12-42
826 **  gives ac=3 and av[1] should match "12-42" (whence the "av+1" call
827 **  of CMDgetrange).
828 **
829 **  rp->Low and rp->High will contain the values of the range.
830 **  *DidReply will be true if this function sends an answer.
831 */
832 bool
CMDgetrange(int ac,char * av[],ARTRANGE * rp,bool * DidReply)833 CMDgetrange(int ac, char *av[], ARTRANGE *rp, bool *DidReply)
834 {
835     char		*p;
836 
837     *DidReply = false;
838 
839     if (ac == 1) {
840         /* No arguments, do only current article. */
841         if (ARTnumber < ARTlow || ARTnumber > ARThigh) {
842             Reply("%d Current article number %lu is invalid\r\n",
843                   NNTP_FAIL_ARTNUM_INVALID, ARTnumber);
844             *DidReply = true;
845             return false;
846         }
847         rp->High = rp->Low = ARTnumber;
848         return true;
849     }
850 
851     /* Check the syntax. */
852     if (!IsValidRange(av[1])) {
853         Reply("%d Syntax error in range\r\n", NNTP_ERR_SYNTAX);
854         *DidReply = true;
855         return false;
856     }
857 
858     /* Got just a single number? */
859     if ((p = strchr(av[1], '-')) == NULL) {
860         rp->Low = rp->High = atol(av[1]);
861         return true;
862     }
863 
864     /* "-" becomes "\0" and we parse the low water mark.
865      * Note that atol() returns 0 if no valid number
866      * is found at the beginning of *p. */
867     *p++ = '\0';
868     rp->Low = atol(av[1]);
869 
870     /* Adjust the low water mark. */
871     if (rp->Low < ARTlow)
872         rp->Low = ARTlow;
873 
874     /* Parse and adjust the high water mark.
875      * "12-" gives everything from 12 to the end.
876      * We do not bother about "42-12" or "42-0" in this function. */
877     if ((*p == '\0') || ((rp->High = atol(p)) > ARThigh))
878         rp->High = ARThigh;
879 
880     p--;
881     *p = '-';
882 
883     return true;
884 }
885 
886 
887 /*
888 **  Apply virtual hosting to an Xref: field.
889 */
890 static char *
vhost_xref(char * p)891 vhost_xref(char *p)
892 {
893     char *space;
894     size_t offset;
895     char *field = NULL;
896 
897     space = strchr(p, ' ');
898     if (space == NULL) {
899         warn("malformed Xref: `%s'", p);
900         goto fail;
901     }
902     offset = space - p;
903     space = strchr(p + offset, ' ');
904     if (space == NULL) {
905         warn("malformed Xref: `%s'", p);
906         goto fail;
907     }
908     field = concat(PERMaccessconf->domain, space, NULL);
909  fail:
910     free(p);
911     return field;
912 }
913 
914 
915 /*
916 **  Dump parts of the overview database with the OVER command.
917 **  The legacy XOVER is also kept, with its specific behaviour.
918 */
919 void
CMDover(int ac,char * av[])920 CMDover(int ac, char *av[])
921 {
922     bool	        DidReply, HasNotReplied;
923     ARTRANGE		range;
924     struct timeval	stv, etv;
925     ARTNUM		artnum;
926     void		*handle;
927     char		*data, *r;
928     const char		*p, *q;
929     int			len, useIOb = 0;
930     TOKEN		token;
931     struct cvector *vector = NULL;
932     bool                xover, mid;
933 
934     xover = (strcasecmp(av[0], "XOVER") == 0);
935     mid = (ac > 1 && IsValidMessageID(av[1], true, laxmid));
936 
937     if (mid && !xover) {
938         /* FIXME:  We still do not support OVER MSGID, sorry! */
939         Reply("%d Overview by message-ID unsupported\r\n", NNTP_ERR_UNAVAILABLE);
940         return;
941     }
942 
943     /* Check the syntax of the arguments first.
944      * We do not accept a message-ID for XOVER, contrary to OVER.  A range
945      * is accepted for both of them. */
946     if (ac > 1 && !IsValidRange(av[1])) {
947         /* It is better to check for a range before a message-ID because
948          * '<' could have been forgotten and the error message should then
949          * report a syntax error in the message-ID. */
950         if (xover || isdigit((unsigned char) av[1][0]) || av[1][0] == '-') {
951             Reply("%d Syntax error in range\r\n", NNTP_ERR_SYNTAX);
952             return;
953         } else if (!mid) {
954             Reply("%d Syntax error in message-ID\r\n", NNTP_ERR_SYNTAX);
955             return;
956         }
957     }
958 
959     /* Trying to read. */
960     if (GRPcount == 0) {
961         Reply("%d Not in a newsgroup\r\n", NNTP_FAIL_NO_GROUP);
962         return;
963     }
964 
965     /* Check authorizations.  If a range is requested (not a message-ID),
966      * we check whether the group is still readable. */
967     if (!PERMcanread || (!mid && PERMgroupmadeinvalid)) {
968 	Reply("%d Read access denied\r\n",
969               PERMcanauthenticate ? NNTP_FAIL_AUTH_NEEDED : NNTP_ERR_ACCESS);
970 	return;
971     }
972 
973     /* Parse range.  CMDgetrange() correctly sets the range when
974      * there is no arguments. */
975     if (!CMDgetrange(ac, av, &range, &DidReply))
976         if (DidReply)
977             return;
978 
979     if (PERMaccessconf->nnrpdoverstats) {
980         OVERcount++;
981         gettimeofday(&stv, NULL);
982     }
983     if ((handle = (void *)OVopensearch(GRPcur, range.Low, range.High)) == NULL) {
984         /* The response code for OVER is different if a range is provided.
985          * Note that XOVER answers OK. */
986         if (ac > 1)
987             Reply("%d No articles in %s\r\n",
988                   xover ? NNTP_OK_OVER : NNTP_FAIL_ARTNUM_NOTFOUND, av[1]);
989         else
990             Reply("%d No such article number %lu\r\n",
991                   xover ? NNTP_OK_OVER : NNTP_FAIL_ARTNUM_NOTFOUND, ARTnumber);
992         if (xover)
993             Printf(".\r\n");
994         return;
995     }
996     if (PERMaccessconf->nnrpdoverstats) {
997 	gettimeofday(&etv, NULL);
998 	OVERtime+=(etv.tv_sec - stv.tv_sec) * 1000;
999 	OVERtime+=(etv.tv_usec - stv.tv_usec) / 1000;
1000     }
1001 
1002     if (PERMaccessconf->nnrpdoverstats)
1003 	gettimeofday(&stv, NULL);
1004 
1005     /* If OVSTATICSEARCH is true, then the data returned by OVsearch is only
1006        valid until the next call to OVsearch.  In this case, we must use
1007        SendIOb because it copies the data. */
1008     OVctl(OVSTATICSEARCH, &useIOb);
1009 
1010     HasNotReplied = true;
1011     while (OVsearch(handle, &artnum, &data, &len, &token, NULL)) {
1012 	if (PERMaccessconf->nnrpdoverstats) {
1013 	    gettimeofday(&etv, NULL);
1014 	    OVERtime+=(etv.tv_sec - stv.tv_sec) * 1000;
1015 	    OVERtime+=(etv.tv_usec - stv.tv_usec) / 1000;
1016 	}
1017 	if (len == 0 || (PERMaccessconf->nnrpdcheckart && !ARTinstorebytoken(token))) {
1018 	    if (PERMaccessconf->nnrpdoverstats) {
1019 		OVERmiss++;
1020 		gettimeofday(&stv, NULL);
1021 	    }
1022 	    continue;
1023 	}
1024 	if (PERMaccessconf->nnrpdoverstats) {
1025 	    OVERhit++;
1026 	    OVERsize += len;
1027 	}
1028 
1029         if (HasNotReplied) {
1030             if (ac > 1)
1031                 Reply("%d Overview information for %s follows\r\n",
1032                       NNTP_OK_OVER, av[1]);
1033             else
1034                 Reply("%d Overview information for %lu follows\r\n",
1035                       NNTP_OK_OVER, ARTnumber);
1036             fflush(stdout);
1037             HasNotReplied = false;
1038         }
1039 
1040         vector = overview_split(data, len, NULL, vector);
1041         r = overview_get_standard_header(vector, OVERVIEW_MESSAGE_ID);
1042         if (r == NULL) {
1043             if (PERMaccessconf->nnrpdoverstats) {
1044                 gettimeofday(&stv, NULL);
1045             }
1046             continue;
1047         }
1048 	cache_add(HashMessageID(r), token);
1049 	free(r);
1050 	if (VirtualPathlen > 0 && overhdr_xref != -1) {
1051             if ((size_t)(overhdr_xref + 1) >= vector->count) {
1052                 if (PERMaccessconf->nnrpdoverstats) {
1053                     gettimeofday(&stv, NULL);
1054                 }
1055                 continue;
1056             }
1057 	    p = vector->strings[overhdr_xref] + sizeof("Xref: ") - 1;
1058 	    while ((p < data + len) && *p == ' ')
1059 		++p;
1060 	    q = memchr(p, ' ', data + len - p);
1061             if (q == NULL) {
1062                 if (PERMaccessconf->nnrpdoverstats) {
1063                     gettimeofday(&stv, NULL);
1064                 }
1065                 continue;
1066             }
1067             /* Copy the virtual path without its final '!'. */
1068 	    if(useIOb) {
1069 		SendIOb(data, p - data);
1070 		SendIOb(VirtualPath, VirtualPathlen - 1);
1071 		SendIOb(q, len - (q - data));
1072 	    } else {
1073 		SendIOv(data, p - data);
1074 		SendIOv(VirtualPath, VirtualPathlen - 1);
1075 		SendIOv(q, len - (q - data));
1076 	    }
1077 	} else {
1078 	    if(useIOb)
1079 		SendIOb(data, len);
1080 	    else
1081 		SendIOv(data, len);
1082 	}
1083 	if (PERMaccessconf->nnrpdoverstats)
1084 	    gettimeofday(&stv, NULL);
1085     }
1086 
1087     if (PERMaccessconf->nnrpdoverstats) {
1088         gettimeofday(&etv, NULL);
1089         OVERtime+=(etv.tv_sec - stv.tv_sec) * 1000;
1090         OVERtime+=(etv.tv_usec - stv.tv_usec) / 1000;
1091     }
1092 
1093     if (vector)
1094         cvector_free(vector);
1095 
1096     if (HasNotReplied) {
1097         /* The response code for OVER is different if a range is provided.
1098          * Note that XOVER answers OK. */
1099         if (ac > 1)
1100             Reply("%d No articles in %s\r\n",
1101                   xover ? NNTP_OK_OVER : NNTP_FAIL_ARTNUM_NOTFOUND, av[1]);
1102         else
1103             Reply("%d No such article number %lu\r\n",
1104                   xover ? NNTP_OK_OVER : NNTP_FAIL_ARTNUM_NOTFOUND, ARTnumber);
1105         if (xover)
1106             Printf(".\r\n");
1107     } else {
1108         if(useIOb) {
1109             SendIOb(".\r\n", 3);
1110             PushIOb();
1111         } else {
1112             SendIOv(".\r\n", 3);
1113             PushIOv();
1114         }
1115     }
1116 
1117     if (PERMaccessconf->nnrpdoverstats)
1118 	gettimeofday(&stv, NULL);
1119     OVclosesearch(handle);
1120     if (PERMaccessconf->nnrpdoverstats) {
1121         gettimeofday(&etv, NULL);
1122         OVERtime+=(etv.tv_sec - stv.tv_sec) * 1000;
1123         OVERtime+=(etv.tv_usec - stv.tv_usec) / 1000;
1124     }
1125 
1126 }
1127 
1128 /*
1129 **  Access specific fields from an article with HDR.
1130 **  The legacy XHDR and XPAT are also kept, with their specific behaviours.
1131 */
1132 void
CMDpat(int ac,char * av[])1133 CMDpat(int ac, char *av[])
1134 {
1135     char	        *p;
1136     unsigned long      	i;
1137     ARTRANGE		range;
1138     bool		IsBytes, IsLines;
1139     bool                IsMetaBytes, IsMetaLines;
1140     bool		DidReply, HasNotReplied;
1141     const char		*header;
1142     char		*pattern;
1143     char		*text;
1144     int			Overview;
1145     ARTNUM		artnum;
1146     char		buff[SPOOLNAMEBUFF];
1147     void                *handle;
1148     char                *data;
1149     int                 len;
1150     TOKEN               token;
1151     struct cvector *vector = NULL;
1152     bool                hdr, mid;
1153 
1154     hdr = (strcasecmp(av[0], "HDR") == 0);
1155     mid = (ac > 2 && IsValidMessageID(av[2], true, laxmid));
1156 
1157     /* Check the syntax of the arguments first. */
1158     if (ac > 2 && !IsValidRange(av[2])) {
1159         /* It is better to check for a range before a message-ID because
1160          * '<' could have been forgotten and the error message should then
1161          * report a syntax error in the message-ID. */
1162         if (isdigit((unsigned char) av[2][0]) || av[2][0] == '-') {
1163             Reply("%d Syntax error in range\r\n", NNTP_ERR_SYNTAX);
1164             return;
1165         } else if (!mid) {
1166             Reply("%d Syntax error in message-ID\r\n", NNTP_ERR_SYNTAX);
1167             return;
1168         }
1169     }
1170 
1171     header = av[1];
1172 
1173     /* If metadata is asked for, convert it to headers that
1174      * the overview database knows. */
1175     IsBytes = (strcasecmp(header, "Bytes") == 0);
1176     IsLines = (strcasecmp(header, "Lines") == 0);
1177     IsMetaBytes = (strcasecmp(header, ":bytes") == 0);
1178     IsMetaLines = (strcasecmp(header, ":lines") == 0);
1179     /* Make these changes because our overview database does
1180      * not currently know metadata names. */
1181     if (IsMetaBytes)
1182         header = "Bytes";
1183     if (IsMetaLines)
1184         header = "Lines";
1185 
1186     /* We only allow :bytes and :lines for metadata. */
1187     if (!IsMetaLines && !IsMetaBytes) {
1188         p = av[1];
1189         p++;
1190         if (strncasecmp(header, ":", 1) == 0 && IsValidHeaderName(p)) {
1191             Reply("%d Unsupported metadata request\r\n",
1192                   NNTP_ERR_UNAVAILABLE);
1193             return;
1194         } else if (!IsValidHeaderName(header)) {
1195             Reply("%d Syntax error in header name\r\n", NNTP_ERR_SYNTAX);
1196             return;
1197         }
1198     }
1199 
1200     /* Trying to read. */
1201     if (GRPcount == 0 && !mid) {
1202         Reply("%d Not in a newsgroup\r\n", NNTP_FAIL_NO_GROUP);
1203         return;
1204     }
1205 
1206     /* Check authorizations.  If a range is requested (not a message-ID),
1207      * we check whether the group is still readable. */
1208     if (!PERMcanread || (!mid && PERMgroupmadeinvalid)) {
1209         Reply("%d Read access denied\r\n",
1210               PERMcanauthenticate ? NNTP_FAIL_AUTH_NEEDED : NNTP_ERR_ACCESS);
1211         return;
1212     }
1213 
1214     if (ac > 3) /* Necessarily XPAT. */
1215 	pattern = Glom(&av[3]);
1216     else
1217 	pattern = NULL;
1218 
1219     /* We will only do the loop once.  It is just in order to easily break. */
1220     do {
1221 	/* Message-ID specified? */
1222 	if (mid) {
1223             /* FIXME:  We do not handle metadata requests by message-ID. */
1224             if (hdr && (IsMetaBytes || IsMetaLines)) {
1225                 Reply("%d Metadata requests by message-ID unsupported\r\n",
1226                       NNTP_ERR_UNAVAILABLE);
1227                 break;
1228             }
1229 
1230 	    p = av[2];
1231 	    if (!ARTopenbyid(p, &artnum, false)) {
1232 		Reply("%d No such article\r\n", NNTP_FAIL_MSGID_NOTFOUND);
1233 		break;
1234 	    }
1235 
1236             if (!PERMartok()) {
1237                 ARTclose();
1238                 Reply("%d Read access denied for this article\r\n",
1239                       PERMcanauthenticate ? NNTP_FAIL_AUTH_NEEDED : NNTP_ERR_ACCESS);
1240                 break;
1241             }
1242 
1243 	    Reply("%d Header information for %s follows (from the article)\r\n",
1244                    hdr ? NNTP_OK_HDR : NNTP_OK_HEAD, av[1]);
1245 
1246 	    if ((text = GetHeader(av[1], false)) != NULL
1247 		&& (!pattern || uwildmat_simple(text, pattern)))
1248 		Printf("%s %s\r\n", hdr ? "0" : p, text);
1249             else if (hdr) {
1250                 /* We always have to answer something with HDR. */
1251                 Printf("0 \r\n");
1252             }
1253 
1254 	    ARTclose();
1255 	    Printf(".\r\n");
1256 	    break;
1257 	}
1258 
1259         /* Parse range.  CMDgetrange() correctly sets the range when
1260          * there is no arguments. */
1261         if (!CMDgetrange(ac - 1, av + 1, &range, &DidReply))
1262             if (DidReply)
1263                 break;
1264 
1265 	/* In overview? */
1266         Overview = overview_index(header, OVextra);
1267 
1268         HasNotReplied = true;
1269 
1270 	/* Not in overview, we have to fish headers out from the articles. */
1271 	if (Overview < 0 || IsBytes || IsLines) {
1272 	    for (i = range.Low; i <= range.High && range.High > 0; i++) {
1273 		if (!ARTopen(i))
1274 		    continue;
1275                 if (HasNotReplied) {
1276                     Reply("%d Header information for %s follows (from articles)\r\n",
1277                           hdr ? NNTP_OK_HDR : NNTP_OK_HEAD, av[1]);
1278                     HasNotReplied = false;
1279                 }
1280 		p = GetHeader(header, false);
1281 		if (p && (!pattern || uwildmat_simple(p, pattern))) {
1282 		    snprintf(buff, sizeof(buff), "%lu ", i);
1283 		    SendIOb(buff, strlen(buff));
1284 		    SendIOb(p, strlen(p));
1285 		    SendIOb("\r\n", 2);
1286 		} else if (hdr) {
1287                     /* We always have to answer something with HDR. */
1288                     snprintf(buff, sizeof(buff), "%lu \r\n", i);
1289                     SendIOb(buff, strlen(buff));
1290                 }
1291                 ARTclose();
1292 	    }
1293             if (HasNotReplied) {
1294                 if (hdr) {
1295                     if (ac > 2)
1296                         Reply("%d No articles in %s\r\n",
1297                               NNTP_FAIL_ARTNUM_NOTFOUND, av[2]);
1298                     else
1299                         Reply("%d No such article number %lu\r\n",
1300                               NNTP_FAIL_ARTNUM_NOTFOUND, ARTnumber);
1301                 } else {
1302                     Reply("%d No header information for %s follows (from articles)\r\n",
1303                           NNTP_OK_HEAD, av[1]);
1304                     Printf(".\r\n");
1305                 }
1306                 break;
1307             } else {
1308                 SendIOb(".\r\n", 3);
1309             }
1310 	    PushIOb();
1311 	    break;
1312 	}
1313 
1314 	/* Okay then, we can grab values from the overview database. */
1315 	handle = (void *)OVopensearch(GRPcur, range.Low, range.High);
1316 	if (handle == NULL) {
1317             if (hdr) {
1318                 if (ac > 2)
1319                     Reply("%d No articles in %s\r\n",
1320                           NNTP_FAIL_ARTNUM_NOTFOUND, av[2]);
1321                 else
1322                     Reply("%d No such article number %lu\r\n",
1323                           NNTP_FAIL_ARTNUM_NOTFOUND, ARTnumber);
1324             } else {
1325                 Reply("%d No header information for %s follows (from overview)\r\n",
1326                       NNTP_OK_HEAD, av[1]);
1327                 Printf(".\r\n");
1328             }
1329 	    break;
1330 	}
1331 
1332 	while (OVsearch(handle, &artnum, &data, &len, &token, NULL)) {
1333 	    if (len == 0 || (PERMaccessconf->nnrpdcheckart
1334 		&& !ARTinstorebytoken(token)))
1335 		continue;
1336             if (HasNotReplied) {
1337                 Reply("%d Header or metadata information for %s follows (from overview)\r\n",
1338                       hdr ? NNTP_OK_HDR : NNTP_OK_HEAD, av[1]);
1339                 HasNotReplied = false;
1340             }
1341 	    vector = overview_split(data, len, NULL, vector);
1342             if (Overview < OVERVIEW_MAX) {
1343                 p = overview_get_standard_header(vector, Overview);
1344             } else {
1345                 p = overview_get_extra_header(vector, header);
1346             }
1347 	    if (p != NULL) {
1348 		if (PERMaccessconf->virtualhost &&
1349 			   Overview == overhdr_xref) {
1350 		    p = vhost_xref(p);
1351 		    if (p == NULL) {
1352                         if (hdr) {
1353                             snprintf(buff, sizeof(buff), "%lu \r\n", artnum);
1354                             SendIOb(buff, strlen(buff));
1355                         }
1356 			continue;
1357                     }
1358 		}
1359 		if (!pattern || uwildmat_simple(p, pattern)) {
1360 		    snprintf(buff, sizeof(buff), "%lu ", artnum);
1361 		    SendIOb(buff, strlen(buff));
1362 		    SendIOb(p, strlen(p));
1363 		    SendIOb("\r\n", 2);
1364 		}
1365                 /* No need to have another condition for HDR because
1366                  * pattern is NULL for it, and p is not NULL here. */
1367 		free(p);
1368 	    } else if (hdr) {
1369                 snprintf(buff, sizeof(buff), "%lu \r\n", artnum);
1370                 SendIOb(buff, strlen(buff));
1371             }
1372 	}
1373         if (HasNotReplied) {
1374             if (hdr) {
1375                 if (ac > 2)
1376                     Reply("%d No articles in %s\r\n",
1377                           NNTP_FAIL_ARTNUM_NOTFOUND, av[2]);
1378                 else
1379                     Reply("%d Current article number %lu is invalid\r\n",
1380                           NNTP_FAIL_ARTNUM_INVALID, ARTnumber);
1381             } else {
1382                 Reply("%d No header or metadata information for %s follows (from overview)\r\n",
1383                       NNTP_OK_HEAD, av[1]);
1384                 Printf(".\r\n");
1385             }
1386             break;
1387         } else {
1388             SendIOb(".\r\n", 3);
1389         }
1390 	PushIOb();
1391 	OVclosesearch(handle);
1392     } while (0);
1393 
1394     if (vector)
1395 	cvector_free(vector);
1396 
1397     if (pattern)
1398 	free(pattern);
1399 }
1400