Rocco Rutte:
[apps/madmutt.git] / nntp / nntp.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1998 Brandon Long <blong@fiction.net>
4  * Copyright (C) 1999 Andrej Gritsenko <andrej@lucky.net>
5  * Copyright (C) 2000-2002 Vsevolod Volkov <vvv@mutt.org.ua>
6  *
7  * This file is part of mutt-ng, see http://www.muttng.org/.
8  * It's licensed under the GNU General Public License,
9  * please see the file GPL in the top level source directory.
10  */
11
12 #ifdef HAVE_CONFIG_H
13 #include <config.h>
14 #endif
15
16 #include "mutt.h"
17 #include "mutt_curses.h"
18 #include "sort.h"
19 #include "mx.h"
20 #include "mx_nntp.h"
21 #include "mime.h"
22 #include "rfc1524.h"
23 #include "rfc2047.h"
24 #include "nntp.h"
25
26 #ifdef HAVE_PGP
27 #include "pgp.h"
28 #endif
29
30 #ifdef HAVE_SMIME
31 #include "smime.h"
32 #endif
33
34 #include "lib/mem.h"
35 #include "lib/str.h"
36 #include "lib/intl.h"
37
38 #include <unistd.h>
39 #include <string.h>
40 #include <ctype.h>
41 #include <stdlib.h>
42
43 static unsigned int _checked = 0;
44
45 #ifdef DEBUG
46 static void nntp_error (const char *where, const char *msg)
47 {
48   dprint (1,
49           (debugfile, "nntp_error(): unexpected response in %s: %s\n", where,
50            msg));
51 }
52 #endif /* DEBUG */
53
54 static int nntp_auth (NNTP_SERVER * serv)
55 {
56   CONNECTION *conn = serv->conn;
57   char buf[STRING];
58   unsigned char flags = conn->account.flags;
59
60   if (mutt_account_getuser (&conn->account) || !conn->account.user[0] ||
61       mutt_account_getpass (&conn->account) || !conn->account.pass[0]) {
62     conn->account.flags = flags;
63     return -2;
64   }
65
66   mutt_message _("Logging in...");
67
68   snprintf (buf, sizeof (buf), "AUTHINFO USER %s\r\n", conn->account.user);
69   mutt_socket_write (conn, buf);
70   if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) {
71     conn->account.flags = flags;
72     return -1;
73   }
74
75 #ifdef DEBUG
76   /* don't print the password unless we're at the ungodly debugging level */
77   if (debuglevel < M_SOCK_LOG_FULL)
78     dprint (M_SOCK_LOG_CMD, (debugfile, "> AUTHINFO PASS *\n"));
79 #endif
80   snprintf (buf, sizeof (buf), "AUTHINFO PASS %s\r\n", conn->account.pass);
81   mutt_socket_write_d (conn, buf, M_SOCK_LOG_FULL);
82   if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) {
83     conn->account.flags = flags;
84     return -1;
85   }
86
87   if (safe_strncmp ("281", buf, 3)) {
88     conn->account.flags = flags;
89     mutt_error _("Login failed.");
90
91     sleep (2);
92     return -3;
93   }
94
95   return 0;
96 }
97
98 static int nntp_connect_error (NNTP_SERVER * serv)
99 {
100   serv->status = NNTP_NONE;
101   mutt_socket_close (serv->conn);
102   mutt_error _("Server closed connection!");
103
104   sleep (2);
105   return -1;
106 }
107
108 static int nntp_connect_and_auth (NNTP_SERVER * serv)
109 {
110   CONNECTION *conn = serv->conn;
111   char buf[STRING];
112   int rc;
113
114   serv->status = NNTP_NONE;
115
116   if (mutt_socket_open (conn) < 0)
117     return -1;
118
119   if (mutt_socket_readln (buf, sizeof (buf), conn) < 0)
120     return nntp_connect_error (serv);
121
122   if (!safe_strncmp ("200", buf, 3))
123     mutt_message (_("Connected to %s. Posting ok."), conn->account.host);
124   else if (!safe_strncmp ("201", buf, 3))
125     mutt_message (_("Connected to %s. Posting NOT ok."), conn->account.host);
126   else {
127     mutt_socket_close (conn);
128     mutt_remove_trailing_ws (buf);
129     mutt_error ("%s", buf);
130     sleep (2);
131     return -1;
132   }
133
134   sleep (1);
135
136   /* Tell INN to switch to mode reader if it isn't so. Ignore all
137      returned codes and messages. */
138   mutt_socket_write (conn, "MODE READER\r\n");
139   if (mutt_socket_readln (buf, sizeof (buf), conn) < 0)
140     return nntp_connect_error (serv);
141
142   mutt_socket_write (conn, "STAT\r\n");
143   if (mutt_socket_readln (buf, sizeof (buf), conn) < 0)
144     return nntp_connect_error (serv);
145
146   if (!(conn->account.flags & M_ACCT_USER) && safe_strncmp ("480", buf, 3)) {
147     serv->status = NNTP_OK;
148     return 0;
149   }
150
151   rc = nntp_auth (serv);
152   if (rc == -1)
153     return nntp_connect_error (serv);
154   if (rc == -2) {
155     mutt_socket_close (conn);
156     serv->status = NNTP_BYE;
157     return -1;
158   }
159   if (rc < 0) {
160     mutt_socket_close (conn);
161     mutt_error _("Login failed.");
162
163     sleep (2);
164     return -1;
165   }
166   serv->status = NNTP_OK;
167   return 0;
168 }
169
170 static int nntp_attempt_features (NNTP_SERVER * serv)
171 {
172   char buf[LONG_STRING];
173   CONNECTION *conn = serv->conn;
174
175   mutt_socket_write (conn, "LISTGROUP\r\n");
176   if (mutt_socket_readln (buf, sizeof (buf), conn) < 0)
177     return (nntp_connect_error (serv));
178   if (safe_strncmp ("500", buf, 3))
179     serv->hasLISTGROUP = 1;
180
181   mutt_socket_write (conn, "XOVER\r\n");
182   if (mutt_socket_readln (buf, sizeof (buf), conn) < 0)
183     return nntp_connect_error (serv);
184   if (safe_strncmp ("500", buf, 3))
185     serv->hasXOVER = 1;
186
187   mutt_socket_write (conn, "XPAT\r\n");
188   if (mutt_socket_readln (buf, sizeof (buf), conn) < 0)
189     return nntp_connect_error (serv);
190   if (safe_strncmp ("500", buf, 3))
191     serv->hasXPAT = 1;
192
193   mutt_socket_write (conn, "XGTITLE +\r\n");
194   if (mutt_socket_readln (buf, sizeof (buf), conn) < 0)
195     return nntp_connect_error (serv);
196   if (safe_strncmp ("500", buf, 3))
197     serv->hasXGTITLE = 1;
198
199   if (!safe_strncmp ("282", buf, 3)) {
200     do {
201       if (mutt_socket_readln (buf, sizeof (buf), conn) < 0)
202         return nntp_connect_error (serv);
203     } while (!(buf[0] == '.' && buf[1] == '\0'));
204   }
205
206   return 0;
207 }
208
209 static int nntp_open_connection (NNTP_SERVER * serv)
210 {
211   if (serv->status == NNTP_OK)
212     return 0;
213   if (serv->status == NNTP_BYE)
214     return -1;
215   if (nntp_connect_and_auth (serv) < 0)
216     return -1;
217   if (nntp_attempt_features (serv) < 0)
218     return -1;
219   return 0;
220 }
221
222 static int nntp_reconnect (NNTP_SERVER * serv)
223 {
224   char buf[SHORT_STRING];
225
226   mutt_socket_close (serv->conn);
227
228   FOREVER {
229     if (nntp_connect_and_auth (serv) == 0)
230       return 0;
231
232     snprintf (buf, sizeof (buf), _("Connection to %s lost. Reconnect?"),
233               serv->conn->account.host);
234     if (query_quadoption (OPT_NNTPRECONNECT, buf) != M_YES) {
235       serv->status = NNTP_BYE;
236       return -1;
237     }
238   }
239 }
240
241 /* Send data from line[LONG_STRING] and receive answer to same line */
242 static int mutt_nntp_query (NNTP_DATA * data, char *line, size_t linelen)
243 {
244   char buf[LONG_STRING];
245   int done = TRUE;
246
247   if (data->nserv->status == NNTP_BYE)
248     return -1;
249
250   do {
251     if (*line) {
252       mutt_socket_write (data->nserv->conn, line);
253     }
254     else if (data->group) {
255       snprintf (buf, sizeof (buf), "GROUP %s\r\n", data->group);
256       mutt_socket_write (data->nserv->conn, buf);
257     }
258
259     done = TRUE;
260     if (mutt_socket_readln (buf, sizeof (buf), data->nserv->conn) < 0) {
261       if (nntp_reconnect (data->nserv) < 0)
262         return -1;
263
264       if (data->group) {
265         snprintf (buf, sizeof (buf), "GROUP %s\r\n", data->group);
266         mutt_socket_write (data->nserv->conn, buf);
267         if (mutt_socket_readln (buf, sizeof (buf), data->nserv->conn) < 0)
268           return -1;
269       }
270       if (*line)
271         done = FALSE;
272     }
273     else if ((!safe_strncmp ("480", buf, 3)) && nntp_auth (data->nserv) < 0)
274       return -1;
275   } while (!done);
276
277   strfcpy (line, buf, linelen);
278   return 0;
279 }
280
281 /*
282  * This function calls  funct(*line, *data)  for each received line,
283  * funct(NULL, *data)  if  rewind(*data)  needs, exits when fail or done.
284  * Returned codes:
285  *  0 - successful,
286  *  1 - correct but not performed (may be, have to be continued),
287  * -1 - conection lost,
288  * -2 - invalid command or execution error,
289  * -3 - error in funct(*line, *data).
290  */
291 static int mutt_nntp_fetch (NNTP_DATA * nntp_data, char *query, char *msg,
292                             int (*funct) (char *, void *), void *data,
293                             int tagged)
294 {
295   char buf[LONG_STRING];
296   char *inbuf, *p;
297   int done = FALSE;
298   int chunk, line;
299   size_t lenbuf = 0;
300   int ret;
301
302   do {
303     strfcpy (buf, query, sizeof (buf));
304     if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) < 0)
305       return -1;
306     if (buf[0] == '5')
307       return -2;
308     if (buf[0] != '2')
309       return 1;
310
311     ret = 0;
312     line = 0;
313     inbuf = safe_malloc (sizeof (buf));
314
315     FOREVER {
316       chunk = mutt_socket_readln_d (buf, sizeof (buf), nntp_data->nserv->conn,
317                                     M_SOCK_LOG_HDR);
318       if (chunk < 0)
319         break;
320
321       p = buf;
322       if (!lenbuf && buf[0] == '.') {
323         if (buf[1] == '\0') {
324           done = TRUE;
325           break;
326         }
327         if (buf[1] == '.')
328           p++;
329       }
330
331       strfcpy (inbuf + lenbuf, p, sizeof (buf));
332
333       if (chunk >= sizeof (buf)) {
334         lenbuf += safe_strlen (p);
335       }
336       else {
337         line++;
338         if (msg && ReadInc && (line % ReadInc == 0)) {
339           if (tagged)
340             mutt_message (_("%s (tagged: %d) %d"), msg, tagged, line);
341           else
342             mutt_message ("%s %d", msg, line);
343         }
344
345         if (ret == 0 && funct (inbuf, data) < 0)
346           ret = -3;
347         lenbuf = 0;
348       }
349
350       safe_realloc (&inbuf, lenbuf + sizeof (buf));
351     }
352     FREE (&inbuf);
353     funct (NULL, data);
354   }
355   while (!done);
356   return ret;
357 }
358
359 static int nntp_read_tempfile (char *line, void *file)
360 {
361   FILE *f = (FILE *) file;
362
363   if (!line)
364     rewind (f);
365   else {
366     fputs (line, f);
367     if (fputc ('\n', f) == EOF)
368       return -1;
369   }
370   return 0;
371 }
372
373 static void nntp_parse_xref (CONTEXT * ctx, char *group, char *xref,
374                              HEADER * h)
375 {
376   register char *p, *b;
377   register char *colon = NULL;
378
379   b = p = xref;
380   while (*p) {
381     /* skip to next word */
382     b = p;
383     while (*b && ((*b == ' ') || (*b == '\t')))
384       b++;
385     p = b;
386     colon = NULL;
387     /* skip to end of word */
388     while (*p && (*p != ' ') && (*p != '\t')) {
389       if (*p == ':')
390         colon = p;
391       p++;
392     }
393     if (*p) {
394       *p = '\0';
395       p++;
396     }
397     if (colon) {
398       *colon = '\0';
399       colon++;
400       nntp_get_status (ctx, h, p, atoi (colon));
401       if (h && h->article_num == 0 && safe_strcmp (group, b) == 0)
402         h->article_num = atoi (colon);
403     }
404   }
405 }
406
407 /*
408  * returns:
409  *  0 on success
410  *  1 if article not found
411  * -1 if read or write error on tempfile or socket
412  */
413 static int nntp_read_header (CONTEXT * ctx, const char *msgid,
414                              int article_num)
415 {
416   NNTP_DATA *nntp_data = ((NNTP_DATA *) ctx->data);
417   FILE *f;
418   char buf[LONG_STRING];
419   char tempfile[_POSIX_PATH_MAX];
420   int ret;
421   HEADER *h = ctx->hdrs[ctx->msgcount];
422
423   mutt_mktemp (tempfile);
424   if (!(f = safe_fopen (tempfile, "w+")))
425     return -1;
426
427   if (!msgid)
428     snprintf (buf, sizeof (buf), "HEAD %d\r\n", article_num);
429   else
430     snprintf (buf, sizeof (buf), "HEAD %s\r\n", msgid);
431
432   ret = mutt_nntp_fetch (nntp_data, buf, NULL, nntp_read_tempfile, f, 0);
433   if (ret) {
434 #ifdef DEBUG
435     if (ret != -1)
436       dprint (1, (debugfile, "nntp_read_header: %s\n", buf));
437 #endif
438     fclose (f);
439     unlink (tempfile);
440     return (ret == -1 ? -1 : 1);
441   }
442
443   h->article_num = article_num;
444   h->env = mutt_read_rfc822_header (f, h, 0, 0);
445   fclose (f);
446   unlink (tempfile);
447
448   if (h->env->xref != NULL)
449     nntp_parse_xref (ctx, nntp_data->group, h->env->xref, h);
450   else if (h->article_num == 0 && msgid) {
451     snprintf (buf, sizeof (buf), "STAT %s\r\n", msgid);
452     if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) == 0)
453       h->article_num = atoi (buf + 4);
454   }
455
456   return 0;
457 }
458
459 static int parse_description (char *line, void *n)
460 {
461 #define news ((NNTP_SERVER *) n)
462   register char *d = line;
463   NNTP_DATA *data;
464
465   if (!line)
466     return 0;
467   while (*d && *d != '\t' && *d != ' ')
468     d++;
469   *d = 0;
470   d++;
471   while (*d && (*d == '\t' || *d == ' '))
472     d++;
473   dprint (2, (debugfile, "group: %s, desc: %s\n", line, d));
474   if ((data = (NNTP_DATA *) hash_find (news->newsgroups, line)) != NULL &&
475       safe_strcmp (d, data->desc)) {
476     FREE (&data->desc);
477     data->desc = safe_strdup (d);
478   }
479   return 0;
480 #undef news
481 }
482
483 static void nntp_get_desc (NNTP_DATA * data, char *mask, char *msg)
484 {
485   char buf[STRING];
486
487   if (!option (OPTLOADDESC) || !data || !data->nserv)
488     return;
489
490   /* Get newsgroup description, if we can */
491   if (data->nserv->hasXGTITLE)
492     snprintf (buf, sizeof (buf), "XGTITLE %s\r\n", mask);
493   else
494     snprintf (buf, sizeof (buf), "LIST NEWSGROUPS %s\r\n", mask);
495   if (mutt_nntp_fetch (data, buf, msg, parse_description, data->nserv, 0) !=
496       0) {
497 #ifdef DEBUG
498     nntp_error ("nntp_get_desc()", buf);
499 #endif
500   }
501 }
502
503 /*
504  * XOVER returns a tab separated list of:
505  * id|subject|from|date|Msgid|references|bytes|lines|xref
506  *
507  * This has to duplicate some of the functionality of 
508  * mutt_read_rfc822_header(), since it replaces the call to that (albeit with
509  * a limited number of headers which are "parsed" by placement in the list)
510  */
511 static int nntp_parse_xover (CONTEXT * ctx, char *buf, HEADER * hdr)
512 {
513   NNTP_DATA *nntp_data = (NNTP_DATA *) ctx->data;
514   char *p, *b;
515   int x, done = 0;
516
517   hdr->env = mutt_new_envelope ();
518   hdr->env->newsgroups = safe_strdup (nntp_data->group);
519   hdr->content = mutt_new_body ();
520   hdr->content->type = TYPETEXT;
521   hdr->content->subtype = safe_strdup ("plain");
522   hdr->content->encoding = ENC7BIT;
523   hdr->content->disposition = DISPINLINE;
524   hdr->content->length = -1;
525   b = p = buf;
526
527   for (x = 0; !done && x < 9; x++) {
528     /* if from file, need to skip newline character */
529     while (*p && *p != '\n' && *p != '\t')
530       p++;
531     if (!*p)
532       done++;
533     *p = '\0';
534     p++;
535     switch (x) {
536     case 0:
537
538       hdr->article_num = atoi (b);
539       nntp_get_status (ctx, hdr, NULL, hdr->article_num);
540       break;
541     case 1:
542       hdr->env->subject = safe_strdup (b);
543       /* Now we need to do the things which would normally be done in 
544        * mutt_read_rfc822_header() */
545       if (hdr->env->subject) {
546         regmatch_t pmatch[1];
547
548         rfc2047_decode (&hdr->env->subject);
549
550         if (regexec (ReplyRegexp.rx, hdr->env->subject, 1, pmatch, 0) == 0)
551           hdr->env->real_subj = hdr->env->subject + pmatch[0].rm_eo;
552         else
553           hdr->env->real_subj = hdr->env->subject;
554       }
555       break;
556     case 2:
557       rfc822_free_address (&hdr->env->from);
558       hdr->env->from = rfc822_parse_adrlist (hdr->env->from, b);
559       rfc2047_decode_adrlist (hdr->env->from);
560       break;
561     case 3:
562       hdr->date_sent = mutt_parse_date (b, hdr);
563       hdr->received = hdr->date_sent;
564       break;
565     case 4:
566       FREE (&hdr->env->message_id);
567       hdr->env->message_id = safe_strdup (b);
568       break;
569     case 5:
570       mutt_free_list (&hdr->env->references);
571       hdr->env->references = mutt_parse_references (b, 0);
572       break;
573     case 6:
574       hdr->content->length = atoi (b);
575       break;
576     case 7:
577       hdr->lines = atoi (b);
578       break;
579     case 8:
580       if (!hdr->read)
581         FREE (&hdr->env->xref);
582       b = b + 6;                /* skips the "Xref: " */
583       hdr->env->xref = safe_strdup (b);
584       nntp_parse_xref (ctx, nntp_data->group, b, hdr);
585     }
586     if (!*p)
587       return -1;
588     b = p;
589   }
590   return 0;
591 }
592
593 typedef struct {
594   CONTEXT *ctx;
595   unsigned int base;
596   unsigned int first;
597   unsigned int last;
598   unsigned short *messages;
599   char *msg;
600 } FETCH_CONTEXT;
601
602 #define fc ((FETCH_CONTEXT *) c)
603 static int _nntp_fetch_numbers (unsigned int num, void *c)
604 {
605   if (num < fc->base || num > fc->last)
606     return 0;
607   fc->messages[num - fc->base] = 1;
608   return 0;
609 }
610 static int nntp_fetch_numbers (char *line, void *c)
611 {
612   if (!line)
613     return 0;
614   return (_nntp_fetch_numbers ((unsigned int) atoi (line), c));
615 }
616
617 static int add_xover_line (char *line, void *c)
618 {
619   unsigned int num, total;
620   CONTEXT *ctx = fc->ctx;
621   NNTP_DATA *data = (NNTP_DATA *) ctx->data;
622
623   if (!line)
624     return 0;
625
626   if (ctx->msgcount >= ctx->hdrmax)
627     mx_alloc_memory (ctx);
628   ctx->hdrs[ctx->msgcount] = mutt_new_header ();
629   ctx->hdrs[ctx->msgcount]->index = ctx->msgcount;
630
631   nntp_parse_xover (ctx, line, ctx->hdrs[ctx->msgcount]);
632   num = ctx->hdrs[ctx->msgcount]->article_num;
633
634   if (num >= fc->first && num <= fc->last && fc->messages[num - fc->base]) {
635     ctx->msgcount++;
636     if (num > data->lastLoaded)
637       data->lastLoaded = num;
638     num = num - fc->first + 1;
639     total = fc->last - fc->first + 1;
640     if (!ctx->quiet && fc->msg && ReadInc && (num % ReadInc == 0))
641       mutt_message ("%s %d/%d", fc->msg, num, total);
642   }
643   else
644     mutt_free_header (&ctx->hdrs[ctx->msgcount]);       /* skip it */
645
646   return 0;
647 }
648
649 #undef fc
650
651 static int nntp_fetch_headers (CONTEXT * ctx, unsigned int first,
652                                unsigned int last)
653 {
654   char buf[HUGE_STRING];
655   char *msg = _("Fetching message headers...");
656   NNTP_DATA *nntp_data = ((NNTP_DATA *) ctx->data);
657   int ret;
658   int num;
659   int oldmsgcount;
660   unsigned int current;
661   FILE *f;
662   FETCH_CONTEXT fc;
663
664   /* if empty group or nothing to do */
665   if (!last || first > last)
666     return 0;
667
668   /* fetch list of articles */
669   mutt_message _("Fetching list of articles...");
670
671   fc.ctx = ctx;
672   fc.base = first;
673   fc.last = last;
674   fc.messages = safe_calloc (last - first + 1, sizeof (unsigned short));
675   if (nntp_data->nserv->hasLISTGROUP) {
676     snprintf (buf, sizeof (buf), "LISTGROUP %s\r\n", nntp_data->group);
677     if (mutt_nntp_fetch (nntp_data, buf, NULL, nntp_fetch_numbers, &fc, 0) !=
678         0) {
679       mutt_error (_("LISTGROUP command failed: %s"), buf);
680       sleep (2);
681 #ifdef DEBUG
682       nntp_error ("nntp_fetch_headers()", buf);
683 #endif
684       FREE (&fc.messages);
685       return -1;
686     }
687   }
688   else {
689     /* mutt_nntp_query() issues a 'GROUP nntp_data->group' 
690      * command on its own if !*buf */
691     buf[0] = '\0';
692     mutt_nntp_query (nntp_data, buf, sizeof (buf));
693     if (sscanf (buf + 4, "%d %u %u %s", &num, &fc.first, &fc.last, buf) != 4) {
694       mutt_error (_("GROUP command failed: %s"), buf);
695       FREE (&fc.messages);
696       return (-1);
697     }
698     else {
699       for (num = fc.first; num < fc.last; num++)
700         _nntp_fetch_numbers (num, &fc);
701     }
702   }
703
704   /* CACHE: must be loaded xover cache here */
705   num = nntp_data->lastCached - first + 1;
706   if (option (OPTNEWSCACHE) && nntp_data->cache && num > 0) {
707     nntp_cache_expand (buf, nntp_data->cache);
708     mutt_message _("Fetching headers from cache...");
709
710     if ((f = safe_fopen (buf, "r"))) {
711       int r = 0;
712
713       /* counting number of lines */
714       while (fgets (buf, sizeof (buf), f) != NULL)
715         r++;
716       rewind (f);
717       while (r > num && fgets (buf, sizeof (buf), f) != NULL)
718         r--;
719       oldmsgcount = ctx->msgcount;
720       fc.first = first;
721       fc.last = first + num - 1;
722       fc.msg = NULL;
723       while (fgets (buf, sizeof (buf), f) != NULL)
724         add_xover_line (buf, &fc);
725       fclose (f);
726       nntp_data->lastLoaded = fc.last;
727       first = fc.last + 1;
728       if (ctx->msgcount > oldmsgcount)
729         mx_update_context (ctx, ctx->msgcount - oldmsgcount);
730     }
731     else
732       nntp_delete_cache (nntp_data);
733   }
734   num = last - first + 1;
735   if (num <= 0) {
736     FREE (&fc.messages);
737     return 0;
738   }
739
740   /*
741    * Without XOVER, we have to fetch each article header and parse
742    * it.  With XOVER, we ask for all of them
743    */
744   mutt_message (msg);
745   if (nntp_data->nserv->hasXOVER) {
746     oldmsgcount = ctx->msgcount;
747     fc.first = first;
748     fc.last = last;
749     fc.msg = msg;
750     snprintf (buf, sizeof (buf), "XOVER %d-%d\r\n", first, last);
751     ret = mutt_nntp_fetch (nntp_data, buf, NULL, add_xover_line, &fc, 0);
752     if (ctx->msgcount > oldmsgcount)
753       mx_update_context (ctx, ctx->msgcount - oldmsgcount);
754     if (ret != 0) {
755       mutt_error (_("XOVER command failed: %s"), buf);
756 #ifdef DEBUG
757       nntp_error ("nntp_fetch_headers()", buf);
758 #endif
759       FREE (&fc.messages);
760       return -1;
761     }
762     /* fetched OK */
763   }
764   else
765     for (current = first; current <= last; current++) {
766       HEADER *h;
767
768       ret = current - first + 1;
769       mutt_message ("%s %d/%d", msg, ret, num);
770
771       if (!fc.messages[current - fc.base])
772         continue;
773
774       if (ctx->msgcount >= ctx->hdrmax)
775         mx_alloc_memory (ctx);
776       h = ctx->hdrs[ctx->msgcount] = mutt_new_header ();
777       h->index = ctx->msgcount;
778
779       ret = nntp_read_header (ctx, NULL, current);
780       if (ret == 0) {           /* Got article. Fetch next header */
781         nntp_get_status (ctx, h, NULL, h->article_num);
782         ctx->msgcount++;
783         mx_update_context (ctx, 1);
784       }
785       else
786         mutt_free_header (&h);  /* skip it */
787       if (ret == -1) {
788         FREE (&fc.messages);
789         return -1;
790       }
791
792       if (current > nntp_data->lastLoaded)
793         nntp_data->lastLoaded = current;
794     }
795   FREE (&fc.messages);
796   nntp_data->lastLoaded = last;
797   mutt_clear_error ();
798   return 0;
799 }
800
801 /* 
802  * currently, nntp "mailbox" is "newsgroup"
803  */
804 int nntp_open_mailbox (CONTEXT * ctx)
805 {
806   NNTP_DATA *nntp_data;
807   NNTP_SERVER *serv;
808   char buf[HUGE_STRING];
809   char server[LONG_STRING];
810   int count = 0;
811   unsigned int first;
812   ACCOUNT acct;
813
814   if (nntp_parse_url (ctx->path, &acct, buf, sizeof (buf)) < 0 || !*buf) {
815     mutt_error (_("%s is an invalid newsgroup specification!"), ctx->path);
816     mutt_sleep (2);
817     return -1;
818   }
819
820   server[0] = '\0';
821   nntp_expand_path (server, sizeof (server), &acct);
822   if (!(serv = mutt_select_newsserver (server)) || serv->status != NNTP_OK)
823     return -1;
824
825   CurrentNewsSrv = serv;
826
827   /* create NNTP-specific state struct if nof found in list */
828   if ((nntp_data = (NNTP_DATA *) hash_find (serv->newsgroups, buf)) == NULL) {
829     nntp_data = safe_calloc (1, sizeof (NNTP_DATA) + safe_strlen (buf) + 1);
830     nntp_data->group = (char *) nntp_data + sizeof (NNTP_DATA);
831     strcpy (nntp_data->group, buf);
832     hash_insert (serv->newsgroups, nntp_data->group, nntp_data, 0);
833     nntp_add_to_list (serv, nntp_data);
834   }
835   ctx->data = nntp_data;
836   nntp_data->nserv = serv;
837
838   mutt_message (_("Selecting %s..."), nntp_data->group);
839
840   if (!nntp_data->desc) {
841     nntp_get_desc (nntp_data, nntp_data->group, NULL);
842     if (nntp_data->desc)
843       nntp_save_cache_index (serv);
844   }
845
846   buf[0] = 0;
847   if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) < 0) {
848 #ifdef DEBUG
849     nntp_error ("nntp_open_mailbox()", buf);
850 #endif
851     return -1;
852   }
853
854   if (safe_strncmp ("211", buf, 3)) {
855     LIST *l = serv->list;
856
857     /* GROUP command failed */
858     if (!safe_strncmp ("411", buf, 3)) {
859       mutt_error (_("Newsgroup %s not found on server %s"),
860                   nntp_data->group, serv->conn->account.host);
861
862       /* CACHE: delete cache and line from .index */
863       nntp_delete_cache (nntp_data);
864       hash_delete (serv->newsgroups, nntp_data->group, NULL,
865                    nntp_delete_data);
866       while (l && l->data != (void *) nntp_data)
867         l = l->next;
868       if (l)
869         l->data = NULL;
870
871       sleep (2);
872     }
873
874     return -1;
875   }
876
877   sscanf (buf + 4, "%d %u %u %s", &count, &nntp_data->firstMessage,
878           &nntp_data->lastMessage, buf);
879
880   nntp_data->deleted = 0;
881
882   time (&serv->check_time);
883
884   /*
885    * Check for max adding context. If it is greater than $nntp_context,
886    * strip off extra articles
887    */
888   first = nntp_data->firstMessage;
889   if (NntpContext && nntp_data->lastMessage - first + 1 > NntpContext)
890     first = nntp_data->lastMessage - NntpContext + 1;
891   if (first)
892     nntp_data->lastLoaded = first - 1;
893   return nntp_fetch_headers (ctx, first, nntp_data->lastMessage);
894 }
895
896 int nntp_fetch_message (MESSAGE * msg, CONTEXT * ctx, int msgno)
897 {
898   char buf[LONG_STRING];
899   char path[_POSIX_PATH_MAX];
900   NNTP_CACHE *cache;
901   char *m = _("Fetching message...");
902   int ret;
903
904   /* see if we already have the message in our cache */
905   cache =
906     &((NNTP_DATA *) ctx->data)->acache[ctx->hdrs[msgno]->index %
907                                        NNTP_CACHE_LEN];
908
909   /* if everything is fine, assign msg->fp and return */
910   if (cache->path && cache->index == ctx->hdrs[msgno]->index &&
911       (msg->fp = fopen (cache->path, "r")))
912     return 0;
913
914   /* clear the previous entry */
915   unlink (cache->path);
916   free (cache->path);
917
918   mutt_message (m);
919
920   cache->index = ctx->hdrs[msgno]->index;
921   mutt_mktemp (path);
922   cache->path = safe_strdup (path);
923   if (!(msg->fp = safe_fopen (path, "w+"))) {
924     FREE (&cache->path);
925     return -1;
926   }
927
928   if (ctx->hdrs[msgno]->article_num == 0)
929     snprintf (buf, sizeof (buf), "ARTICLE %s\r\n",
930               ctx->hdrs[msgno]->env->message_id);
931   else
932     snprintf (buf, sizeof (buf), "ARTICLE %d\r\n",
933               ctx->hdrs[msgno]->article_num);
934
935   ret = mutt_nntp_fetch ((NNTP_DATA *) ctx->data, buf, m, nntp_read_tempfile,
936                          msg->fp, ctx->tagged);
937   if (ret == 1) {
938     mutt_error (_("Article %d not found on server"),
939                 ctx->hdrs[msgno]->article_num);
940     dprint (1, (debugfile, "nntp_fetch_message: %s\n", buf));
941   }
942
943   if (ret) {
944     fclose (msg->fp);
945     unlink (path);
946     FREE (&cache->path);
947     return -1;
948   }
949
950   mutt_free_envelope (&ctx->hdrs[msgno]->env);
951   ctx->hdrs[msgno]->env =
952     mutt_read_rfc822_header (msg->fp, ctx->hdrs[msgno], 0, 0);
953   /* fix content length */
954   fseek (msg->fp, 0, SEEK_END);
955   ctx->hdrs[msgno]->content->length = ftell (msg->fp) -
956     ctx->hdrs[msgno]->content->offset;
957
958   /* this is called in mutt before the open which fetches the message, 
959    * which is probably wrong, but we just call it again here to handle
960    * the problem instead of fixing it.
961    */
962   mutt_parse_mime_message (ctx, ctx->hdrs[msgno]);
963
964   /* These would normally be updated in mx_update_context(), but the 
965    * full headers aren't parsed with XOVER, so the information wasn't
966    * available then.
967    */
968 #if defined(HAVE_PGP) || defined(HAVE_SMIME)
969   ctx->hdrs[msgno]->security = crypt_query (ctx->hdrs[msgno]->content);
970 #endif /* HAVE_PGP || HAVE_SMIME */
971
972   mutt_clear_error ();
973   rewind (msg->fp);
974
975   return 0;
976 }
977
978 /* Post article */
979 int nntp_post (const char *msg)
980 {
981   char buf[LONG_STRING];
982   size_t len;
983   FILE *f;
984   NNTP_DATA *nntp_data;
985
986   if (Context && Context->magic == M_NNTP)
987     nntp_data = (NNTP_DATA *) Context->data;
988   else {
989     if (!(CurrentNewsSrv = mutt_select_newsserver (NewsServer)) ||
990         !CurrentNewsSrv->list || !CurrentNewsSrv->list->data) {
991       mutt_error (_("Can't post article. No connection to news server."));
992       return -1;
993     }
994     nntp_data = (NNTP_DATA *) CurrentNewsSrv->list->data;
995   }
996
997   if (!(f = safe_fopen (msg, "r"))) {
998     mutt_error (_("Can't post article. Unable to open %s"), msg);
999     return -1;
1000   }
1001
1002   strfcpy (buf, "POST\r\n", sizeof (buf));
1003   if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) < 0) {
1004     mutt_error (_("Can't post article. Connection to %s lost."),
1005                 nntp_data->nserv->conn->account.host);
1006     return -1;
1007   }
1008   if (buf[0] != '3') {
1009     mutt_error (_("Can't post article: %s"), buf);
1010     return -1;
1011   }
1012
1013   buf[0] = '.';
1014   buf[1] = '\0';
1015   while (fgets (buf + 1, sizeof (buf) - 2, f) != NULL) {
1016     len = safe_strlen (buf);
1017     if (buf[len - 1] == '\n') {
1018       buf[len - 1] = '\r';
1019       buf[len] = '\n';
1020       len++;
1021       buf[len] = '\0';
1022     }
1023     if (buf[1] == '.')
1024       mutt_socket_write_d (nntp_data->nserv->conn, buf, M_SOCK_LOG_HDR);
1025     else
1026       mutt_socket_write_d (nntp_data->nserv->conn, buf + 1, M_SOCK_LOG_HDR);
1027   }
1028   fclose (f);
1029
1030   if (buf[safe_strlen (buf) - 1] != '\n')
1031     mutt_socket_write_d (nntp_data->nserv->conn, "\r\n", M_SOCK_LOG_HDR);
1032   mutt_socket_write_d (nntp_data->nserv->conn, ".\r\n", M_SOCK_LOG_HDR);
1033   if (mutt_socket_readln (buf, sizeof (buf), nntp_data->nserv->conn) < 0) {
1034     mutt_error (_("Can't post article. Connection to %s lost."),
1035                 nntp_data->nserv->conn->account.host);
1036     return -1;
1037   }
1038   if (buf[0] != '2') {
1039     mutt_error (_("Can't post article: %s"), buf);
1040     return -1;
1041   }
1042
1043   return 0;
1044 }
1045
1046 /* nntp_logout_all: close all open connections. */
1047 void nntp_logout_all (void)
1048 {
1049   char buf[LONG_STRING];
1050   CONNECTION *conn;
1051   CONNECTION *tmp;
1052
1053   conn = mutt_socket_head ();
1054
1055   while (conn) {
1056     tmp = conn;
1057
1058     if (conn->account.type == M_ACCT_TYPE_NNTP) {
1059       mutt_message (_("Closing connection to %s..."), conn->account.host);
1060       mutt_socket_write (conn, "QUIT\r\n");
1061       mutt_socket_readln (buf, sizeof (buf), conn);
1062       mutt_clear_error ();
1063       mutt_socket_close (conn);
1064
1065       mutt_socket_free (tmp);
1066     }
1067
1068     conn = conn->next;
1069   }
1070 }
1071
1072 static void nntp_free_acache (NNTP_DATA * data)
1073 {
1074   int i;
1075
1076   for (i = 0; i < NNTP_CACHE_LEN; i++) {
1077     if (data->acache[i].path) {
1078       unlink (data->acache[i].path);
1079       FREE (&data->acache[i].path);
1080     }
1081   }
1082 }
1083
1084 void nntp_delete_data (void *p)
1085 {
1086   NNTP_DATA *data = (NNTP_DATA *) p;
1087
1088   if (!p)
1089     return;
1090   FREE (&data->entries);
1091   FREE (&data->desc);
1092   FREE (&data->cache);
1093   nntp_free_acache (data);
1094   FREE (p);
1095 }
1096
1097 int nntp_sync_mailbox (CONTEXT * ctx)
1098 {
1099   NNTP_DATA *data = ctx->data;
1100
1101   /* CACHE: update cache and .index files */
1102   if ((option (OPTSAVEUNSUB) || data->subscribed))
1103     nntp_save_cache_group (ctx);
1104   nntp_free_acache (data);
1105
1106   data->nserv->check_time = 0;  /* next nntp_check_mailbox() will really check */
1107   return 0;
1108 }
1109
1110 void nntp_fastclose_mailbox (CONTEXT * ctx)
1111 {
1112   NNTP_DATA *data = (NNTP_DATA *) ctx->data, *tmp;
1113
1114   if (!data)
1115     return;
1116   nntp_free_acache (data);
1117   if (!data->nserv || !data->nserv->newsgroups || !data->group)
1118     return;
1119   nntp_save_cache_index (data->nserv);
1120   if ((tmp = hash_find (data->nserv->newsgroups, data->group)) == NULL
1121       || tmp != data)
1122     nntp_delete_data (data);
1123 }
1124
1125 /* commit changes and terminate connection */
1126 int nntp_close_mailbox (CONTEXT * ctx)
1127 {
1128   if (!ctx)
1129     return -1;
1130   mutt_message _("Quitting newsgroup...");
1131
1132   if (ctx->data) {
1133     NNTP_DATA *data = (NNTP_DATA *) ctx->data;
1134     int ret;
1135
1136     if (data->nserv && data->nserv->conn && ctx->unread) {
1137       ret = query_quadoption (OPT_CATCHUP, _("Mark all articles read?"));
1138       if (ret == M_YES)
1139         mutt_newsgroup_catchup (data->nserv, data->group);
1140       else if (ret < 0)
1141         return -1;
1142     }
1143   }
1144   nntp_sync_mailbox (ctx);
1145   if (ctx->data && ((NNTP_DATA *) ctx->data)->nserv) {
1146     NNTP_SERVER *news;
1147
1148     news = ((NNTP_DATA *) ctx->data)->nserv;
1149     newsrc_gen_entries (ctx);
1150     ((NNTP_DATA *) ctx->data)->unread = ctx->unread;
1151     mutt_newsrc_update (news);
1152   }
1153   mutt_clear_error ();
1154   return 0;
1155 }
1156
1157 /* use the GROUP command to poll for new mail */
1158 static int _nntp_check_mailbox (CONTEXT * ctx, NNTP_DATA * nntp_data)
1159 {
1160   char buf[LONG_STRING];
1161   int count = 0;
1162
1163   if (nntp_data->nserv->check_time + NewsPollTimeout > time (NULL))
1164     return 0;
1165
1166   buf[0] = 0;
1167   if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) < 0) {
1168 #ifdef DEBUG
1169     nntp_error ("nntp_check_mailbox()", buf);
1170 #endif
1171     return -1;
1172   }
1173   if (safe_strncmp ("211", buf, 3)) {
1174     buf[0] = 0;
1175     if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) < 0) {
1176 #ifdef DEBUG
1177       nntp_error ("nntp_check_mailbox()", buf);
1178 #endif
1179       return -1;
1180     }
1181   }
1182   if (!safe_strncmp ("211", buf, 3)) {
1183     int first;
1184     int last;
1185
1186     sscanf (buf + 4, "%d %d %d", &count, &first, &last);
1187     nntp_data->firstMessage = first;
1188     nntp_data->lastMessage = last;
1189     if (ctx && last > nntp_data->lastLoaded) {
1190       nntp_fetch_headers (ctx, nntp_data->lastLoaded + 1, last);
1191       time (&nntp_data->nserv->check_time);
1192       return 1;
1193     }
1194     if (!last || (!nntp_data->rc && !nntp_data->lastCached))
1195       nntp_data->unread = count;
1196     else
1197       mutt_newsgroup_stat (nntp_data);
1198     /* active was renumbered? */
1199     if (last < nntp_data->lastLoaded) {
1200       if (!nntp_data->max) {
1201         nntp_data->entries = safe_calloc (5, sizeof (NEWSRC_ENTRY));
1202         nntp_data->max = 5;
1203       }
1204       nntp_data->lastCached = 0;
1205       nntp_data->num = 1;
1206       nntp_data->entries[0].first = 1;
1207       nntp_data->entries[0].last = 0;
1208     }
1209   }
1210
1211   time (&nntp_data->nserv->check_time);
1212   return 0;
1213 }
1214
1215 int nntp_check_mailbox (CONTEXT * ctx)
1216 {
1217   return _nntp_check_mailbox (ctx, (NNTP_DATA *) ctx->data);
1218 }
1219
1220 static int add_group (char *buf, void *serv)
1221 {
1222 #define s ((NNTP_SERVER *) serv)
1223   char group[LONG_STRING], mod, desc[HUGE_STRING];
1224   int first, last;
1225   NNTP_DATA *nntp_data;
1226   static int n = 0;
1227
1228   _checked = n;                 /* _checked have N, where N = number of groups */
1229   if (!buf)                     /* at EOF must be zerouth */
1230     n = 0;
1231
1232   if (!s || !buf)
1233     return 0;
1234
1235   *desc = 0;
1236   sscanf (buf, "%s %d %d %c %[^\n]", group, &last, &first, &mod, desc);
1237   if (!group)
1238     return 0;
1239   if ((nntp_data = (NNTP_DATA *) hash_find (s->newsgroups, group)) == NULL) {
1240     n++;
1241     nntp_data = safe_calloc (1, sizeof (NNTP_DATA) + safe_strlen (group) + 1);
1242     nntp_data->group = (char *) nntp_data + sizeof (NNTP_DATA);
1243     strcpy (nntp_data->group, group);
1244     nntp_data->nserv = s;
1245     if (s->newsgroups->nelem < s->newsgroups->curnelem * 2)
1246       s->newsgroups = hash_resize (s->newsgroups, s->newsgroups->nelem * 2);
1247     hash_insert (s->newsgroups, nntp_data->group, nntp_data, 0);
1248     nntp_add_to_list (s, nntp_data);
1249   }
1250   nntp_data->deleted = 0;
1251   nntp_data->firstMessage = first;
1252   nntp_data->lastMessage = last;
1253   if (mod == 'y')
1254     nntp_data->allowed = 1;
1255   else
1256     nntp_data->allowed = 0;
1257   if (nntp_data->desc)
1258     FREE (&nntp_data->desc);
1259   if (*desc)
1260     nntp_data->desc = safe_strdup (desc);
1261   if (nntp_data->rc || nntp_data->lastCached)
1262     mutt_newsgroup_stat (nntp_data);
1263   else if (nntp_data->lastMessage &&
1264            nntp_data->firstMessage <= nntp_data->lastMessage)
1265     nntp_data->unread = nntp_data->lastMessage - nntp_data->firstMessage + 1;
1266   else
1267     nntp_data->unread = 0;
1268
1269   return 0;
1270 #undef s
1271 }
1272
1273 int nntp_check_newgroups (NNTP_SERVER * serv, int force)
1274 {
1275   char buf[LONG_STRING];
1276   NNTP_DATA nntp_data;
1277   LIST *l;
1278   LIST emp;
1279   time_t now;
1280   struct tm *t;
1281
1282   if (!serv || !serv->newgroups_time)
1283     return -1;
1284
1285   if (nntp_open_connection (serv) < 0)
1286     return -1;
1287
1288   /* check subscribed groups for new news */
1289   if (option (OPTSHOWNEWNEWS)) {
1290     mutt_message _("Checking for new messages...");
1291
1292     for (l = serv->list; l; l = l->next) {
1293       serv->check_time = 0;     /* really check! */
1294       if (l->data && ((NNTP_DATA *) l->data)->subscribed)
1295         _nntp_check_mailbox (NULL, (NNTP_DATA *) l->data);
1296     }
1297   }
1298   else if (!force)
1299     return 0;
1300
1301   mutt_message _("Checking for new newsgroups...");
1302
1303   now = serv->newgroups_time;
1304   time (&serv->newgroups_time);
1305   t = gmtime (&now);
1306   snprintf (buf, sizeof (buf), "NEWGROUPS %02d%02d%02d %02d%02d%02d GMT\r\n",
1307             (t->tm_year % 100), t->tm_mon + 1, t->tm_mday, t->tm_hour,
1308             t->tm_min, t->tm_sec);
1309   nntp_data.nserv = serv;
1310   if (Context && Context->magic == M_NNTP)
1311     nntp_data.group = ((NNTP_DATA *) Context->data)->group;
1312   else
1313     nntp_data.group = NULL;
1314   l = serv->tail;
1315   if (mutt_nntp_fetch (&nntp_data, buf, _("Adding new newsgroups..."),
1316                        add_group, serv, 0) != 0) {
1317 #ifdef DEBUG
1318     nntp_error ("nntp_check_newgroups()", buf);
1319 #endif
1320     return -1;
1321   }
1322
1323   mutt_message _("Loading descriptions...");
1324
1325   if (l)
1326     emp.next = l->next;
1327   else
1328     emp.next = serv->list;
1329   l = &emp;
1330   while (l->next) {
1331     l = l->next;
1332     ((NNTP_DATA *) l->data)->new = 1;
1333     nntp_get_desc ((NNTP_DATA *) l->data, ((NNTP_DATA *) l->data)->group,
1334                    NULL);
1335   }
1336   if (emp.next)
1337     nntp_save_cache_index (serv);
1338   mutt_clear_error ();
1339   return _checked;
1340 }
1341
1342 /* Load list of all newsgroups from cache ALL */
1343 int nntp_get_cache_all (NNTP_SERVER * serv)
1344 {
1345   char buf[HUGE_STRING];
1346   FILE *f;
1347
1348   nntp_cache_expand (buf, serv->cache);
1349   if ((f = safe_fopen (buf, "r"))) {
1350     int i = 0;
1351
1352     while (fgets (buf, sizeof (buf), f) != NULL) {
1353       if (ReadInc && (i % ReadInc == 0))
1354         mutt_message (_("Loading list from cache... %d"), i);
1355       add_group (buf, serv);
1356       i++;
1357     }
1358     add_group (NULL, NULL);
1359     fclose (f);
1360     mutt_clear_error ();
1361     return 0;
1362   }
1363   else {
1364     FREE (&serv->cache);
1365     return -1;
1366   }
1367 }
1368
1369 /* Load list of all newsgroups from active */
1370 int nntp_get_active (NNTP_SERVER * serv)
1371 {
1372   char msg[SHORT_STRING];
1373   NNTP_DATA nntp_data;
1374   LIST *tmp;
1375
1376   if (nntp_open_connection (serv) < 0)
1377     return -1;
1378
1379   snprintf (msg, sizeof (msg),
1380             _("Loading list of all newsgroups on server %s..."),
1381             serv->conn->account.host);
1382   mutt_message (msg);
1383   time (&serv->newgroups_time);
1384   nntp_data.nserv = serv;
1385   nntp_data.group = NULL;
1386
1387   if (mutt_nntp_fetch (&nntp_data, "LIST\r\n", msg, add_group, serv, 0) < 0) {
1388 #ifdef DEBUG
1389     nntp_error ("nntp_get_active()", "LIST\r\n");
1390 #endif
1391     return -1;
1392   }
1393
1394   strfcpy (msg, _("Loading descriptions..."), sizeof (msg));
1395   mutt_message (msg);
1396   nntp_get_desc (&nntp_data, "*", msg);
1397
1398   for (tmp = serv->list; tmp; tmp = tmp->next) {
1399     NNTP_DATA *data = (NNTP_DATA *) tmp->data;
1400
1401     if (data && data->deleted && !data->rc) {
1402       nntp_delete_cache (data);
1403       hash_delete (serv->newsgroups, data->group, NULL, nntp_delete_data);
1404       tmp->data = NULL;
1405     }
1406   }
1407   nntp_save_cache_index (serv);
1408
1409   mutt_clear_error ();
1410   return _checked;
1411 }
1412
1413 /*
1414  * returns -1 if error ocurred while retrieving header,
1415  * number of articles which ones exist in context on success.
1416  */
1417 int nntp_check_msgid (CONTEXT * ctx, const char *msgid)
1418 {
1419   int ret;
1420
1421   /* if msgid is already in context, don't reload them */
1422   if (hash_find (ctx->id_hash, msgid))
1423     return 1;
1424   if (ctx->msgcount == ctx->hdrmax)
1425     mx_alloc_memory (ctx);
1426   ctx->hdrs[ctx->msgcount] = mutt_new_header ();
1427   ctx->hdrs[ctx->msgcount]->index = ctx->msgcount;
1428
1429   mutt_message (_("Fetching %s from server..."), msgid);
1430   ret = nntp_read_header (ctx, msgid, 0);
1431   /* since nntp_read_header() may set read flag, we must reset it */
1432   ctx->hdrs[ctx->msgcount]->read = 0;
1433   if (ret != 0)
1434     mutt_free_header (&ctx->hdrs[ctx->msgcount]);
1435   else {
1436     ctx->msgcount++;
1437     mx_update_context (ctx, 1);
1438     ctx->changed = 1;
1439   }
1440   return ret;
1441 }
1442
1443 typedef struct {
1444   CONTEXT *ctx;
1445   unsigned int num;
1446   unsigned int max;
1447   unsigned int *child;
1448 } CHILD_CONTEXT;
1449
1450 static int check_children (char *s, void *c)
1451 {
1452 #define cc ((CHILD_CONTEXT *) c)
1453   unsigned int i, n;
1454
1455   if (!s || (n = atoi (s)) == 0)
1456     return 0;
1457   for (i = 0; i < cc->ctx->msgcount; i++)
1458     if (cc->ctx->hdrs[i]->article_num == n)
1459       return 0;
1460   if (cc->num >= cc->max)
1461     safe_realloc (&cc->child, sizeof (unsigned int) * (cc->max += 25));
1462   cc->child[cc->num++] = n;
1463
1464   return 0;
1465 #undef cc
1466 }
1467
1468 int nntp_check_children (CONTEXT * ctx, const char *msgid)
1469 {
1470   NNTP_DATA *nntp_data = (NNTP_DATA *) ctx->data;
1471   char buf[STRING];
1472   int i, ret = 0, tmp = 0;
1473   CHILD_CONTEXT cc;
1474
1475   if (!nntp_data || !nntp_data->nserv || !nntp_data->nserv->conn ||
1476       !nntp_data->nserv->conn->account.host)
1477     return -1;
1478   if (nntp_data->firstMessage > nntp_data->lastLoaded)
1479     return 0;
1480   if (!nntp_data->nserv->hasXPAT) {
1481     mutt_error (_("Server %s does not support this operation!"),
1482                 nntp_data->nserv->conn->account.host);
1483     return -1;
1484   }
1485
1486   snprintf (buf, sizeof (buf), "XPAT References %d-%d *%s*\r\n",
1487             nntp_data->firstMessage, nntp_data->lastLoaded, msgid);
1488
1489   cc.ctx = ctx;
1490   cc.num = 0;
1491   cc.max = 25;
1492   cc.child = safe_malloc (sizeof (unsigned int) * 25);
1493   if (mutt_nntp_fetch (nntp_data, buf, NULL, check_children, &cc, 0)) {
1494     FREE (&cc.child);
1495     return -1;
1496   }
1497   /* dont try to read the xover cache. check_children() already
1498    * made sure that we dont have the article, so we need to visit
1499    * the server. Reading the cache at this point is also bad
1500    * because it would duplicate messages */
1501   if (option (OPTNEWSCACHE)) {
1502     tmp++;
1503     unset_option (OPTNEWSCACHE);
1504   }
1505   for (i = 0; i < cc.num; i++) {
1506     if ((ret = nntp_fetch_headers (ctx, cc.child[i], cc.child[i])))
1507       break;
1508     if (ctx->msgcount &&
1509         ctx->hdrs[ctx->msgcount - 1]->article_num == cc.child[i])
1510       ctx->hdrs[ctx->msgcount - 1]->read = 0;
1511   }
1512   if (tmp)
1513     set_option (OPTNEWSCACHE);
1514   FREE (&cc.child);
1515   return ret;
1516 }