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