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