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