1 /*
2 Copyright (C) 2000 Masanao Izumo <mo@goice.co.jp>
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 */
18
19 #include "config.h"
20 #include <stdio.h>
21 #include <stdlib.h>
22
23 #ifdef HAVE_UNISTD_H
24 #include <unistd.h>
25 #endif /* HAVE_UNISTD_H */
26
27 #ifndef NO_STRING_H
28 #include <string.h>
29 #else
30 #include <strings.h>
31 #endif
32 #include <signal.h> /* for SIGALRM */
33
34 #include "libarc/url.h"
35 #include "net.h"
36
37 typedef struct _NewsConnection
38 {
39 char *host;
40 unsigned short port;
41 FILE *fp;
42 SOCKET fd;
43 struct _NewsConnection *next;
44 int status; /* -1, 0, 1 */
45 } NewsConnection;
46
47 #define NNTP_OK_ID '2'
48 #define ALARM_TIMEOUT 10
49 /* #define DEBUG */
50
51 static VOLATILE int timeout_flag = 1;
52 static NewsConnection *connection_cache;
53 static int connection_cache_flag = URL_NEWS_CONN_NO_CACHE;
54
55 typedef struct _URL_news
56 {
57 char common[sizeof(struct _URL)];
58
59 NewsConnection *news;
60 int status; /* for detection '\r?\n.\r?\n'
61 * 1 2 34 5
62 */
63 int eof;
64 } URL_news;
65
66 enum
67 {
68 ARTICLE_STATUS_0,
69 ARTICLE_STATUS_1,
70 ARTICLE_STATUS_2,
71 ARTICLE_STATUS_3,
72 ARTICLE_STATUS_4
73 };
74
75 static int name_news_check(char *url_string);
76 static long url_news_read(URL url, void *buff, long n);
77 static int url_news_fgetc(URL url);
78 static void url_news_close(URL url);
79
80 struct URL_module URL_module_news =
81 {
82 URL_news_t,
83 name_news_check,
84 NULL,
85 url_news_open,
86 NULL
87 };
88
name_news_check(char * s)89 static int name_news_check(char *s)
90 {
91 if(strncmp(s, "news://", 7) == 0 && strchr(s, '@') != NULL)
92 return 1;
93 return 0;
94 }
95
96 /*ARGSUSED*/
timeout(int sig)97 static void timeout(int sig)
98 {
99 timeout_flag = 1;
100 }
101
close_news_server(NewsConnection * news)102 static void close_news_server(NewsConnection *news)
103 {
104 if(news->fd != (SOCKET)-1)
105 {
106 socket_write(news->fd, "QUIT\r\n", 6);
107 closesocket(news->fd);
108 }
109 if(news->fp != NULL)
110 socket_fclose(news->fp);
111 free(news->host);
112 news->status = -1;
113 }
114
open_news_server(char * host,unsigned short port)115 static NewsConnection *open_news_server(char *host, unsigned short port)
116 {
117 NewsConnection *p;
118 char buff[512];
119
120 for(p = connection_cache; p != NULL; p = p->next)
121 {
122 if(p->status == 0 && strcmp(p->host, host) == 0 && p->port == port)
123 {
124 p->status = 1;
125 return p;
126 }
127 }
128 for(p = connection_cache; p != NULL; p = p->next)
129 if(p->status == -1)
130 break;
131 if(p == NULL)
132 {
133 if((p = (NewsConnection *)malloc(sizeof(NewsConnection))) == NULL)
134 return NULL;
135 p->next = connection_cache;
136 connection_cache = p;
137 p->status = -1;
138 }
139
140 if((p->host = strdup(host)) == NULL)
141 return NULL;
142 p->port = port;
143
144 #ifdef __W32__
145 timeout_flag = 0;
146 p->fd = open_socket(host, port);
147 #else
148 timeout_flag = 0;
149 signal(SIGALRM, timeout);
150 alarm(ALARM_TIMEOUT);
151 p->fd = open_socket(host, port);
152 alarm(0);
153 signal(SIGALRM, SIG_DFL);
154 #endif /* __W32__ */
155
156 if(p->fd == (SOCKET)-1)
157 {
158 int save_errno;
159
160 VOLATILE_TOUCH(timeout_flag);
161 #ifdef ETIMEDOUT
162 if(timeout_flag)
163 errno = ETIMEDOUT;
164 #endif /* ETIMEDOUT */
165 if(errno)
166 url_errno = errno;
167 else
168 {
169 url_errno = URLERR_CANTOPEN;
170 errno = ENOENT;
171 }
172 #ifdef DEBUG
173 perror(host);
174 #endif /* DEBUG */
175
176 save_errno = errno;
177 free(p->host);
178 errno = save_errno;
179
180 return NULL;
181 }
182
183 if((p->fp = socket_fdopen(p->fd, "rb")) == NULL)
184 {
185 url_errno = errno;
186 closesocket(p->fd);
187 free(p->host);
188 errno = url_errno;
189 return NULL;
190 }
191
192 buff[0] = '\0';
193 if(socket_fgets(buff, sizeof(buff), p->fp) == NULL)
194 {
195 url_errno = errno;
196 closesocket(p->fd);
197 socket_fclose(p->fp);
198 free(p->host);
199 errno = url_errno;
200 return NULL;
201 }
202
203 #ifdef DEBUG
204 printf("Connect status: %s", buff);
205 #endif /* DEBUG */
206
207 if(buff[0] != NNTP_OK_ID)
208 {
209 closesocket(p->fd);
210 socket_fclose(p->fp);
211 free(p->host);
212 url_errno = URLERR_CANTOPEN;
213 errno = ENOENT;
214 return NULL;
215 }
216 p->status = 1;
217 return p;
218 }
219
url_news_connection_cache(int flag)220 int url_news_connection_cache(int flag)
221 {
222 NewsConnection *p;
223 int oldflag;
224
225 oldflag = connection_cache_flag;
226
227 switch(flag)
228 {
229 case URL_NEWS_CONN_NO_CACHE:
230 case URL_NEWS_CONN_CACHE:
231 connection_cache_flag = flag;
232 break;
233 case URL_NEWS_CLOSE_CACHE:
234 for(p = connection_cache; p != NULL; p = p->next)
235 if(p->status == 0)
236 close_news_server(p);
237 break;
238 case URL_NEWS_GET_FLAG:
239 break;
240 }
241 return oldflag;
242 }
243
url_news_open(char * name)244 URL url_news_open(char *name)
245 {
246 URL_news *url;
247 char *host, *p;
248 unsigned short port;
249 char buff[BUFSIZ], messageID[256];
250 int check_timeout;
251 int i;
252
253 #ifdef DEBUG
254 printf("url_news_open(%s)\n", name);
255 #endif /* DEBUG */
256
257 url = (URL_news *)alloc_url(sizeof(URL_news));
258 if(url == NULL)
259 {
260 url_errno = errno;
261 return NULL;
262 }
263
264 /* common members */
265 URLm(url, type) = URL_news_t;
266 URLm(url, url_read) = url_news_read;
267 URLm(url, url_gets) = NULL;
268 URLm(url, url_fgetc) = url_news_fgetc;
269 URLm(url, url_seek) = NULL;
270 URLm(url, url_tell) = NULL;
271 URLm(url, url_close) = url_news_close;
272
273 /* private members */
274 url->news = NULL;
275 url->status = ARTICLE_STATUS_2;
276 url->eof = 0;
277
278 if(strncmp(name, "news://", 7) == 0)
279 name += 7;
280
281 strncpy(buff, name, sizeof(buff) - 1);
282 buff[sizeof(buff) - 1] = '\0';
283
284 host = buff;
285 for(p = host; *p && *p != ':' && *p != '/'; p++)
286 ;
287 if(*p == ':')
288 {
289 *p++ = '\0'; /* terminate `host' string */
290 port = atoi(p);
291 p = strchr(p, '/');
292 if(p == NULL)
293 {
294 url_errno = URLERR_CANTOPEN;
295 errno = ENOENT;
296 url_news_close((URL)url);
297 return NULL;
298 }
299 }
300 else
301 port = 119;
302 *p++ = '\0'; /* terminate `host' string */
303 if(*p == '<')
304 p++;
305 strncpy(messageID, p, sizeof(messageID) - 1);
306 messageID[sizeof(messageID) - 1] = '\0';
307 i = strlen(messageID);
308 if(i > 0 && messageID[i - 1] == '>')
309 messageID[i - 1] = '\0';
310
311 #ifdef DEBUG
312 printf("messageID: <%s>\n", messageID);
313 #endif /* DEBUG */
314
315 #ifdef DEBUG
316 printf("open(host=`%s', port=`%d')\n", host, port);
317 #endif /* DEBUG */
318
319 if((url->news = open_news_server(host, port)) == NULL)
320 {
321 url_news_close((URL)url);
322 return NULL;
323 }
324
325 check_timeout = 1;
326 retry_article:
327
328 sprintf(buff, "ARTICLE <%s>\r\n", messageID);
329
330 #ifdef DEBUG
331 printf("CMD> %s", buff);
332 #endif /* DEBUG */
333
334 socket_write(url->news->fd, buff, (long)strlen(buff));
335 buff[0] = '\0';
336 if(socket_fgets(buff, sizeof(buff), url->news->fp) == NULL)
337 {
338 if(check_timeout)
339 {
340 check_timeout = 0;
341 close_news_server(url->news);
342 if((url->news = open_news_server(host, port)) != NULL)
343 goto retry_article;
344 }
345 url_news_close((URL)url);
346 url_errno = URLERR_CANTOPEN;
347 errno = ENOENT;
348 return NULL;
349 }
350
351 #ifdef DEBUG
352 printf("CMD< %s", buff);
353 #endif /* DEBUG */
354
355 if(buff[0] != NNTP_OK_ID)
356 {
357 if(check_timeout && strncmp(buff, "503", 3) == 0)
358 {
359 check_timeout = 0;
360 close_news_server(url->news);
361 if((url->news = open_news_server(host, port)) != NULL)
362 goto retry_article;
363 }
364 url_news_close((URL)url);
365 url_errno = errno = ENOENT;
366 return NULL;
367 }
368 return (URL)url;
369 }
370
url_news_close(URL url)371 static void url_news_close(URL url)
372 {
373 URL_news *urlp = (URL_news *)url;
374 NewsConnection *news = urlp->news;
375 int save_errno = errno;
376
377 if(news != NULL)
378 {
379 if(connection_cache_flag == URL_NEWS_CONN_CACHE)
380 news->status = 0;
381 else
382 close_news_server(news);
383 }
384 free(url);
385
386 errno = save_errno;
387 }
388
url_news_read(URL url,void * buff,long size)389 static long url_news_read(URL url, void *buff, long size)
390 {
391 char *p = (char *)buff;
392 long n;
393 int c;
394
395 n = 0;
396 while(n < size)
397 {
398 if((c = url_news_fgetc(url)) == EOF)
399 break;
400 p[n++] = c;
401 }
402 return n;
403 }
404
url_news_fgetc(URL url)405 static int url_news_fgetc(URL url)
406 {
407 URL_news *urlp = (URL_news *)url;
408 NewsConnection *news = urlp->news;
409 int c;
410
411 if(urlp->eof)
412 return EOF;
413 if((c = socket_fgetc(news->fp)) == EOF)
414 {
415 urlp->eof = 1;
416 return EOF;
417 }
418
419 switch(urlp->status)
420 {
421 case ARTICLE_STATUS_0:
422 if(c == '\r')
423 urlp->status = ARTICLE_STATUS_1;
424 else if(c == '\n')
425 urlp->status = ARTICLE_STATUS_2;
426 break;
427
428 case ARTICLE_STATUS_1:
429 if(c == '\n')
430 urlp->status = ARTICLE_STATUS_2;
431 else
432 urlp->status = ARTICLE_STATUS_0;
433 break;
434
435 case ARTICLE_STATUS_2:
436 if(c == '.')
437 urlp->status = ARTICLE_STATUS_3;
438 else
439 urlp->status = ARTICLE_STATUS_0;
440 break;
441
442 case ARTICLE_STATUS_3:
443 if(c == '\r')
444 urlp->status = ARTICLE_STATUS_4;
445 else if(c == '\n')
446 urlp->eof = 1;
447 else
448 urlp->status = ARTICLE_STATUS_0;
449 break;
450
451 case ARTICLE_STATUS_4:
452 if(c == '\n')
453 urlp->eof = 1;
454 break;
455 }
456
457 return c;
458 }
459
460 #ifdef NEWS_MAIN
main(int argc,char ** argv)461 void main(int argc, char **argv)
462 {
463 URL url;
464 char buff[BUFSIZ];
465 int c;
466
467 if(argc != 2)
468 {
469 fprintf(stderr, "Usage: %s news-URL\n", argv[0]);
470 exit(1);
471 }
472 if((url = url_news_open(argv[1])) == NULL)
473 {
474 fprintf(stderr, "Can't open news group: %s\n", argv[1]);
475 exit(1);
476 }
477
478 #if NEWS_MAIN
479 while((c = url_getc(url)) != EOF)
480 putchar(c);
481 #else
482 while((c = url_read(url, buff, sizeof(buff))) > 0)
483 write(1, buff, c);
484 #endif
485 url_close(url);
486 exit(0);
487 }
488 #endif /* NEWS_MAIN */
489