chiark / gitweb /
WIP before any changes resulting from reading SM API stuff
[inn-innduct.git] / backends / nntpget.c
1 /*  $Id: nntpget.c 6135 2003-01-19 01:15:40Z rra $
2 **
3 **  Connect to a remote site, and get news from it to offer to our local
4 **  server.  Read list on stdin, or get it via NEWNEWS command.  Writes
5 **  list of articles still needed to stdout.
6 */
7
8 #include "config.h"
9 #include "clibrary.h"
10 #include "portable/socket.h"
11 #include "portable/time.h"
12 #include <errno.h>
13 #include <sys/stat.h>
14 #include <sys/uio.h>
15
16 /* Needed on AIX 4.1 to get fd_set and friends. */
17 #ifdef HAVE_SYS_SELECT_H
18 # include <sys/select.h>
19 #endif
20
21 #include "inn/history.h"
22 #include "inn/innconf.h"
23 #include "inn/messages.h"
24 #include "libinn.h"
25 #include "nntp.h"
26 #include "paths.h"
27
28 /*
29 **  All information about a site we are connected to.
30 */
31 typedef struct _SITE {
32     char        *Name;
33     int         Rfd;
34     int         Wfd;
35     char        Buffer[BUFSIZ];
36     char        *bp;
37     int         Count;
38 } SITE;
39
40
41 /*
42 **  Global variables.
43 */
44 static struct iovec     SITEvec[2];
45 static char             SITEv1[] = "\r\n";
46 static char             READER[] = "mode reader";
47 static unsigned long    STATgot;
48 static unsigned long    STAToffered;
49 static unsigned long    STATsent;
50 static unsigned long    STATrejected;
51 static struct history   *History;
52
53 \f
54
55 /*
56 **  Read a line of input, with timeout.
57 */
58 static bool
59 SITEread(SITE *sp, char *start)
60 {
61     char        *p;
62     char        *end;
63     struct timeval      t;
64     fd_set              rmask;
65     int                 i;
66     char                c;
67
68     for (p = start, end = &start[NNTP_STRLEN - 1]; ; ) {
69         if (sp->Count == 0) {
70             /* Fill the buffer. */
71     Again:
72             FD_ZERO(&rmask);
73             FD_SET(sp->Rfd, &rmask);
74             t.tv_sec = DEFAULT_TIMEOUT;
75             t.tv_usec = 0;
76             i = select(sp->Rfd + 1, &rmask, NULL, NULL, &t);
77             if (i < 0) {
78                 if (errno == EINTR)
79                     goto Again;
80                 return false;
81             }
82             if (i == 0
83              || !FD_ISSET(sp->Rfd, &rmask)
84              || (sp->Count = read(sp->Rfd, sp->Buffer, sizeof sp->Buffer)) < 0)
85                 return false;
86             if (sp->Count == 0)
87                 return false;
88             sp->bp = sp->Buffer;
89         }
90
91         /* Process next character. */
92         sp->Count--;
93         c = *sp->bp++;
94         if (c == '\n')
95             break;
96         if (p < end)
97             *p++ = c;
98     }
99
100     /* If last two characters are \r\n, kill the \r as well as the \n. */
101     if (p > start && p < end && p[-1] == '\r')
102         p--;
103     *p = '\0';
104     return true;
105 }
106
107
108 /*
109 **  Send a line to the server, adding \r\n.  Don't need to do dot-escape
110 **  since it's only for sending DATA to local site, and the data we got from
111 **  the remote site already is escaped.
112 */
113 static bool
114 SITEwrite(SITE *sp, const char *p, int i)
115 {
116     SITEvec[0].iov_base = (char *) p;
117     SITEvec[0].iov_len = i;
118     return xwritev(sp->Wfd, SITEvec, 2) >= 0;
119 }
120
121
122 static SITE *
123 SITEconnect(char *host)
124 {
125     FILE        *From;
126     FILE        *To;
127     SITE        *sp;
128     int         i;
129
130     /* Connect and identify ourselves. */
131     if (host)
132         i = NNTPconnect(host, NNTP_PORT, &From, &To, (char *)NULL);
133     else {
134         host = innconf->server;
135         if (host == NULL)
136             die("no server specified and server not set in inn.conf");
137         i = NNTPlocalopen(&From, &To, (char *)NULL);
138     }
139     if (i < 0)
140         sysdie("cannot connect to %s", host);
141
142     if (NNTPsendpassword(host, From, To) < 0)
143         sysdie("cannot authenticate to %s", host);
144
145     /* Build the structure. */
146     sp = xmalloc(sizeof(SITE));
147     sp->Name = host;
148     sp->Rfd = fileno(From);
149     sp->Wfd = fileno(To);
150     sp->bp = sp->Buffer;
151     sp->Count = 0;
152     return sp;
153 }
154
155
156 /*
157 **  Send "quit" to a site, and get its reply.
158 */
159 static void
160 SITEquit(SITE *sp)
161 {
162     char        buff[NNTP_STRLEN];
163
164     SITEwrite(sp, "quit", 4);
165     SITEread(sp, buff);
166 }
167
168
169 static bool
170 HIShaveit(char *mesgid)
171 {
172     return HIScheck(History, mesgid);
173 }
174
175
176 static void
177 Usage(const char *p)
178 {
179     warn("%s", p);
180     fprintf(stderr, "Usage: nntpget"
181             " [ -d dist -n grps [-f file | -t time -u file]] host\n");
182     exit(1);
183 }
184
185
186 int
187 main(int ac, char *av[])
188 {
189     char        buff[NNTP_STRLEN];
190     char        mesgid[NNTP_STRLEN];
191     char        tbuff[SMBUF];
192     char        *msgidfile = NULL;
193     int         msgidfd;
194     const char  *Groups;
195     char        *distributions;
196     char        *Since;
197     char        *path;
198     int         i;
199     struct tm   *gt;
200     struct stat Sb;
201     SITE        *Remote;
202     SITE        *Local = NULL;
203     FILE        *F;
204     bool        Offer;
205     bool        Error;
206     bool        Verbose = false;
207     char        *Update;
208     char        *p;
209
210     /* First thing, set up our identity. */
211     message_program_name = "nntpget";
212
213     /* Set defaults. */
214     distributions = NULL;
215     Groups = NULL;
216     Since = NULL;
217     Offer = false;
218     Update = NULL;
219     if (!innconf_read(NULL))
220         exit(1);
221
222     umask(NEWSUMASK);
223
224     /* Parse JCL. */
225     while ((i = getopt(ac, av, "d:f:n:t:ovu:")) != EOF)
226         switch (i) {
227         default:
228             Usage("bad flag");
229             /* NOTREACHED */
230         case 'd':
231             distributions = optarg;
232             break;
233         case 'u':
234             Update = optarg;
235             /* FALLTHROUGH */
236         case 'f':
237             if (Since)
238                 Usage("only one of -f, -t, or -u may be given");
239             if (stat(optarg, &Sb) < 0)
240                 sysdie("cannot stat %s", optarg);
241             gt = gmtime(&Sb.st_mtime);
242             /* Y2K: NNTP Spec currently allows only two digit years. */
243             snprintf(tbuff, sizeof(tbuff), "%02d%02d%02d %02d%02d%02d GMT",
244                     gt->tm_year % 100, gt->tm_mon + 1, gt->tm_mday,
245                     gt->tm_hour, gt->tm_min, gt->tm_sec);
246             Since = tbuff;
247             break;
248         case 'n':
249             Groups = optarg;
250             break;
251         case 'o':
252             /* Open the history file. */
253             path = concatpath(innconf->pathdb, _PATH_HISTORY);
254             History = HISopen(path, innconf->hismethod, HIS_RDONLY);
255             if (!History)
256                 sysdie("cannot open history");
257             free(path);
258             Offer = true;
259             break;
260         case 't':
261             if (Since)
262                 Usage("only one of -t or -f may be given");
263             Since = optarg;
264             break;
265         case 'v':
266             Verbose = true;
267             break;
268         }
269     ac -= optind;
270     av += optind;
271     if (ac != 1)
272         Usage("no host given");
273
274     /* Set up the scatter/gather vectors used by SITEwrite. */
275     SITEvec[1].iov_base = SITEv1;
276     SITEvec[1].iov_len = strlen(SITEv1);
277
278     /* Connect to the remote server. */
279     if ((Remote = SITEconnect(av[0])) == NULL)
280         sysdie("cannot connect to %s", av[0]);
281     if (!SITEwrite(Remote, READER, (int)strlen(READER))
282      || !SITEread(Remote, buff))
283         sysdie("cannot start reading");
284
285     if (Since == NULL) {
286         F = stdin;
287         if (distributions || Groups)
288             Usage("no -d or -n flags allowed when reading stdin");
289     }
290     else {
291         /* Ask the server for a list of what's new. */
292         if (Groups == NULL)
293             Groups = "*";
294         if (distributions)
295             snprintf(buff, sizeof(buff), "NEWNEWS %s %s <%s>",
296                      Groups, Since, distributions);
297         else
298             snprintf(buff, sizeof(buff), "NEWNEWS %s %s", Groups, Since);
299         if (!SITEwrite(Remote, buff, (int)strlen(buff))
300          || !SITEread(Remote, buff))
301             sysdie("cannot start list");
302         if (buff[0] != NNTP_CLASS_OK) {
303             SITEquit(Remote);
304             die("protocol error from %s, got %s", Remote->Name, buff);
305         }
306
307         /* Create a temporary file. */
308         msgidfile = concatpath(innconf->pathtmp, "nntpgetXXXXXX");
309         msgidfd = mkstemp(msgidfile);
310         if (msgidfd < 0)
311             sysdie("cannot create a temporary file");
312         F = fopen(msgidfile, "w+");
313         if (F == NULL)
314             sysdie("cannot open %s", msgidfile);
315
316         /* Read and store the Message-ID list. */
317         for ( ; ; ) {
318             if (!SITEread(Remote, buff)) {
319                 syswarn("cannot read from %s", Remote->Name);
320                 fclose(F);
321                 SITEquit(Remote);
322                 exit(1);
323             }
324             if (strcmp(buff, ".") == 0)
325                 break;
326             if (Offer && HIShaveit(buff))
327                 continue;
328             if (fprintf(F, "%s\n", buff) == EOF || ferror(F)) {
329                 syswarn("cannot write %s", msgidfile);
330                 fclose(F);
331                 SITEquit(Remote);
332                 exit(1);
333             }
334         }
335         if (fflush(F) == EOF) {
336             syswarn("cannot flush %s", msgidfile);
337             fclose(F);
338             SITEquit(Remote);
339             exit(1);
340         }
341         fseeko(F, 0, SEEK_SET);
342     }
343
344     if (Offer) {
345         /* Connect to the local server. */
346         if ((Local = SITEconnect((char *)NULL)) == NULL) {
347             syswarn("cannot connect to local server");
348             fclose(F);
349             exit(1);
350         }
351     }
352
353     /* Loop through the list of Message-ID's. */
354     while (fgets(mesgid, sizeof mesgid, F) != NULL) {
355         STATgot++;
356         if ((p = strchr(mesgid, '\n')) != NULL)
357             *p = '\0';
358
359         if (Offer) {
360             /* See if the local server wants it. */
361             STAToffered++;
362             snprintf(buff, sizeof(buff), "ihave %s", mesgid);
363             if (!SITEwrite(Local, buff, (int)strlen(buff))
364              || !SITEread(Local, buff)) {
365                 syswarn("cannot offer %s", mesgid);
366                 break;
367             }
368             if (atoi(buff) != NNTP_SENDIT_VAL)
369                 continue;
370         }
371
372         /* Try to get the article. */
373         snprintf(buff, sizeof(buff), "article %s", mesgid);
374         if (!SITEwrite(Remote, buff, (int)strlen(buff))
375          || !SITEread(Remote, buff)) {
376             syswarn("cannot get %s", mesgid);
377             printf("%s\n", mesgid);
378             break;
379         }
380         if (atoi(buff) != NNTP_ARTICLE_FOLLOWS_VAL) {
381           if (Offer) {
382               SITEwrite(Local, ".", 1);
383               if (!SITEread(Local, buff)) {
384                   syswarn("no reply after %s", mesgid);
385                   break;
386               }
387           }
388           continue;
389         }
390
391         if (Verbose)
392             notice("%s...", mesgid);
393
394         /* Read each line in the article and write it. */
395         for (Error = false; ; ) {
396             if (!SITEread(Remote, buff)) {
397                 syswarn("cannot read %s from %s", mesgid, Remote->Name);
398                 Error = true;
399                 break;
400             }
401             if (Offer) {
402                 if (!SITEwrite(Local, buff, (int)strlen(buff))) {
403                     syswarn("cannot send %s", mesgid);
404                     Error = true;
405                     break;
406                 }
407             }
408             else
409                 printf("%s\n", buff);
410             if (strcmp(buff, ".") == 0)
411                 break;
412         }
413         if (Error) {
414             printf("%s\n", mesgid);
415             break;
416         }
417         STATsent++;
418
419         /* How did the local server respond? */
420         if (Offer) {
421             if (!SITEread(Local, buff)) {
422                 syswarn("no reply after %s", mesgid);
423                 printf("%s\n", mesgid);
424                 break;
425             }
426             i = atoi(buff);
427             if (i == NNTP_TOOKIT_VAL)
428                 continue;
429             if (i == NNTP_RESENDIT_VAL) {
430                 printf("%s\n", mesgid);
431                 break;
432             }
433             syswarn("%s to %s", buff, mesgid);
434             STATrejected++;
435         }
436     }
437
438     /* Write rest of the list, close the input. */
439     if (!feof(F))
440         while (fgets(mesgid, sizeof mesgid, F) != NULL) {
441             if ((p = strchr(mesgid, '\n')) != NULL)
442                 *p = '\0';
443             printf("%s\n", mesgid);
444             STATgot++;
445         }
446     fclose(F);
447
448     /* Remove our temp file. */
449     if (msgidfile && unlink(msgidfile) < 0)
450         syswarn("cannot remove %s", msgidfile);
451
452     /* All done. */
453     SITEquit(Remote);
454     if (Offer)
455         SITEquit(Local);
456
457     /* Update timestamp file? */
458     if (Update) {
459         if ((F = fopen(Update, "w")) == NULL)
460             sysdie("cannot update %s", Update);
461         fprintf(F, "got %ld offered %ld sent %ld rejected %ld\n",
462                 STATgot, STAToffered, STATsent, STATrejected); 
463         if (ferror(F) || fclose(F) == EOF)
464             sysdie("cannot update %s", Update);
465     }
466
467     exit(0);
468     /* NOTREACHED */
469 }