1 /*
2  * $Id: suck_util.c,v 1.4 2002/06/06 14:46:32 conrads Exp $
3  *
4  * Various routines used by suck program
5  */
6 
7 #include <sys/types.h>
8 #include <glob.h>
9 #include <netinet/in.h>
10 #include <netdb.h>
11 #include <sys/socket.h>
12 #include <string.h>
13 #include <unistd.h>
14 #include <fcntl.h>
15 #include <regex.h>
16 #include <stdlib.h>
17 #include <stdio.h>
18 #include <pwd.h>
19 
20 #include "msuck.h"
21 #include "nntp.h"
22 
23 extern SERVER server[];
24 extern ACTIVE_INFO active[];
25 extern CONNECTION local;
26 extern FILTER filter[];
27 extern FILE *sucklog, *killlog, *errlog;
28 extern int local_active_groups;
29 
30 extern char *errlogpath;
31 extern char *killlogpath;
32 extern char *sucklogpath;
33 extern char *filterspath;
34 extern char *newsrc_path_template;
35 
36 /*
37  * get a socket and try connecting until we get a 200 message, return socket
38  * on success, 0 on failure to connect, or -1 on hard error
39  *
40  * FILE pointers to input/output streams for socket are opened as well.  Output
41  * stream is set to line-buffered mode.
42  *
43  * Note: eats the server's greeting message
44  */
45 int
connect_to_server(const char * servername,CONNECTION * server)46 connect_to_server(const char *servername, CONNECTION *server)
47 {
48 	struct hostent *serverinfo;
49 	struct sockaddr_in serversock;
50 	char linebuf[MAXLINE + 1];
51 
52 	if ((serverinfo = gethostbyname(servername)) == NULL)
53 	{
54 		herror("connect_to_server(): server lookup failed");
55 		return -1;
56 	}
57 	bzero((void *)&serversock, sizeof(serversock));
58 	serversock.sin_family = AF_INET;
59 	serversock.sin_port = htons(119);
60 	bcopy(serverinfo->h_addr, (char *)&serversock.sin_addr, serverinfo->h_length);
61 
62 	for (;;)
63 	{
64 		if ((server->sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1)
65 		{
66 			perror("connect_to_server(): couldn't obtain a new socket");
67 			return -1;
68 		}
69 		if (connect(server->sockfd, (struct sockaddr *) & serversock, sizeof(serversock)) == 0)
70 		{
71 			if ((server->in = fdopen(server->sockfd, "r")) == NULL)
72 			{
73 				perror("connect_to_server(): error on fdopen for input stream");
74 				close(server->sockfd);
75 				return -1;
76 			}
77 			if ((server->out = fdopen(server->sockfd, "w")) == NULL)
78 			{
79 				perror("connect_to_server(): error on fdopen for output stream");
80 				fclose(server->in);
81 				return -1;
82 			}
83 
84 			/*
85 			 * set output stream line buffered so we don't have
86 			 * to fflush() constantly
87 			 */
88 			if (setvbuf(server->out, NULL, _IOLBF, 0) == EOF)
89 			{
90 				perror("connect_to_server(): error changing buffering mode on output stream");
91 				close_server(*server);
92 				return -1;
93 			}
94 			/* try reading the server greeting line */
95 			if (fgets(linebuf, MAXLINE, server->in) == NULL)
96 			{
97 				perror("connect_to_server(): read error on socket in connect_to_server()");
98 				close_server(*server);
99 				return -1;
100 			}
101 			/* should have gotten server greeting */
102 			if (strncmp(linebuf, NNTP_POSTOK, 3) == 0)
103 				return server->sockfd;
104 			else
105 			{
106 				close_server(*server);
107 				sleep(30);
108 			}
109 		}
110 		else
111 		{
112 			perror("connect_to_server(): connection to server failed");
113 			close(server->sockfd);
114 			return 0;
115 		}
116 	}
117 }
118 
119 /*
120  * close connection to server (abort, don't do "quit")
121  */
122 void
close_server(CONNECTION server)123 close_server(CONNECTION server)
124 {
125 	fclose(server.in);
126 	fclose(server.out);
127 }
128 
129 /*
130  * quit a server the "correct" way
131  */
132 void
quit_server(CONNECTION server)133 quit_server(CONNECTION server)
134 {
135 	char linebuf[MAXLINE + 1];
136 
137 	/*
138 	 * send quit command (we may still be in a sane state on the server)
139 	 */
140 	fprintf(server.out, "quit\r\n");
141 
142 	/* eat any server output, look for terminating string */
143 	do
144 	{
145 		if (fgets(linebuf, MAXLINE, server.in) == NULL)
146 		{
147 			/*
148 			 * ignore the cause of any error here, as we may have
149 			 * been sent here *because* of an error already!
150 			 */
151 			break;
152 		}
153 	}
154 	while (strncmp(linebuf, NNTP_GOODBYE_ACK, 3) != 0);
155 
156 	close_server(server);
157 }
158 
159 /*
160  * get active info for group from server,
161  * assumes connection has already been opened
162  *
163  * returns 1 on success or 0
164  */
165 int
get_active_info(CONNECTION server,const char * group,ARTNUM * active_lo,ARTNUM * active_hi)166 get_active_info(CONNECTION server,
167 		const char *group,
168 		ARTNUM *active_lo,
169 		ARTNUM *active_hi)
170 {
171 	char linebuf[MAXLINE + 1];
172 	char grp[MAXGROUP + 1];
173 	int responsecode, numarts;
174 
175 	fprintf(server.out, "mode reader\r\n");
176 	if (fgets(linebuf, MAXLINE, server.in) == NULL)
177 	{
178 		perror("get_active_info(): read error in get_active_info()");
179 		return 0;
180 	}
181 	/* should have gotten second server greeting */
182 	if (strncmp(linebuf, NNTP_POSTOK, 3) != 0)
183 	{
184 		return 0;
185 	}
186 	fprintf(server.out, "group %s\r\n", group);
187 	if (fgets(linebuf, MAXLINE, server.in) == NULL)
188 	{
189 		perror("get_active_info(): read error in get_active_info()");
190 		return 0;
191 	}
192 	/* should have gotten group info */
193 	if (strncmp(linebuf, NNTP_GROUPOK, 3) != 0)
194 	{
195 		return 0;
196 	}
197 	if (sscanf(linebuf, "%d %d %lu %lu %s",
198 		   &responsecode, &numarts, active_lo, active_hi, grp) != 5)
199 	{
200 		perror("get_active_info(): read error in get_active_info()");
201 		return 0;
202 	}
203 	return 1;
204 }
205 
206 /*
207  * Get list of active groups from local server
208  *
209  * Returns number of groups read or -1 on error
210  */
211 int
read_active(void)212 read_active(void)
213 {
214 	char buf[MAXLINE + 1];
215 	int numgroups, n;
216 
217 	if (connect_to_server("localhost", &local) <= 0)
218 	{
219 		fprintf(stderr, "read_active(): can't get active groups from local server\n");
220 		return -1;
221 	}
222 
223 	fprintf(local.out, "list active\r\n");
224 	if (fgets(buf, MAXLINE, local.in) == NULL)
225 	{
226 		perror("read_active(): read error on socket after LIST ACTIVE command");
227 		close_server(local);
228 		return -1;
229 	}
230 	/* should have gotten "215" (list follows) reply */
231 	if (strncmp(buf, NNTP_LIST_FOLLOWS, 3) != 0)
232 	{
233 		fprintf(stderr, "read_active(): unexpected reply to LIST ACTIVE command\n");
234 		quit_server(local);
235 		return -1;
236 	}
237 
238 	/* just read up to the terminating "." */
239 	for (numgroups = 0; numgroups < MAX_LOCAL_GROUPS; ++numgroups)
240 	{
241 		if (fgets(buf, MAXLINE, local.in) == NULL)
242 		{
243 			perror("read_active(): read error on socket while fetching list data");
244 			quit_server(local);
245 			return -1;
246 		}
247 
248 		if (END_OF_OUTPUT(buf))
249 			break;
250 
251 		n = sscanf(buf, "%s ", (char *)&active[numgroups]);
252 
253 		if (n != 1)
254 		{
255 			fprintf(stderr, "read_active(): read error while reading list data\n");
256 			quit_server(local);
257 			return -1;
258 		}
259 	}
260 
261 	quit_server(local);
262 	return numgroups;
263 }
264 
265 /*
266  * convert a newline-termintated numeric string in newsrc
267  * to an article number type
268  */
269 ARTNUM
ptrtonum(char * ptr)270 ptrtonum(char *ptr)
271 {
272 	return (strtoul(ptr, NULL, 10));
273 }
274 
275 /*
276  * convert a space-termintated string in newsrc
277  * to a C string
278  */
279 void
ptrtostr(char * ptr,char * str)280 ptrtostr(char *ptr, char *str)
281 {
282 	while (*ptr != ' ')
283 	{
284 		*str++ = *ptr++;
285 	}
286 	*str = '\0';
287 }
288 
289 /*
290  * update newsrc file entry for a group
291  */
292 void
update_newsrc(char * newsrc,ARTNUM artnum)293 update_newsrc(char *newsrc, ARTNUM artnum)
294 {
295 	char num[MAXART + 1];
296 
297 	sprintf(num, "%010lu", artnum);
298 	memcpy(newsrc, num, MAXART);
299 }
300 
301 /*
302  * delete any temporary files left from an earlier run
303  */
304 void
cleanup(void)305 cleanup(void)
306 {
307 	glob_t globstuff;
308 	int i;
309 
310 	if (glob("/tmp/article.*", 0, NULL, &globstuff) != 0)
311 		perror("cleanup(): glob error");
312 	if (glob("/tmp/fetch.*", GLOB_APPEND, NULL, &globstuff) != 0)
313 		perror("cleanup(): glob error");
314 	if (glob("/tmp/xover.*", GLOB_APPEND, NULL, &globstuff) != 0)
315 		perror("cleanup(): glob error");
316 	if (globstuff.gl_pathc > 0)
317 	{
318 		for (i = 0; globstuff.gl_pathv[i] != NULL; ++i)
319 		{
320 			unlink(globstuff.gl_pathv[i]);
321 		}
322 	}
323 }
324 
325 /*
326  * check to see if a file exists
327  *
328  * returns 1 if file exists, else 0
329  */
330 int
file_exists(const char * pathname)331 file_exists(const char *pathname)
332 {
333 	FILE *f;
334 
335 	if ((f = fopen(pathname, "r")) != NULL)
336 	{
337 		fclose(f);
338 		return 1;
339 	}
340 	else
341 		return 0;
342 }
343 
344 /*
345  * do the equivalent of touch(1)
346  *
347  * Returns -1 if file already exists, else 0
348  */
349 int
touch(const char * pathname)350 touch(const char *pathname)
351 {
352 	int fd;
353 
354 	if ((fd = open(pathname, O_CREAT | O_RDONLY | O_EXCL, 0644)) == -1)
355 	{
356 		return -1;
357 	}
358 	else
359 	{
360 		close(fd);
361 		return 0;
362 	}
363 }
364 
365 /*
366  * open log files
367  */
368 int
open_logs(void)369 open_logs(void)
370 {
371 
372 	if ((sucklog = fopen(sucklogpath, "a")) == NULL)
373 	{
374 		perror("open_logs(): can't open sucklog");
375 		return -1;
376 	}
377 	if (setvbuf(sucklog, NULL, _IOLBF, 0) == EOF)
378 	{
379 		perror("open_logs(): error changing buffering mode on sucklog");
380 		return -1;
381 	}
382 	if ((killlog = fopen(killlogpath, "a")) == NULL)
383 	{
384 		perror("open_logs(): can't open killlog");
385 		return -1;
386 	}
387 	if (setvbuf(killlog, NULL, _IOLBF, 0) == EOF)
388 	{
389 		perror("open_logs(): error changing buffering mode on killlog");
390 		return -1;
391 	}
392 	/* redirect error output to errlog */
393 	if ((errlog = freopen(errlogpath, "a", stderr)) == NULL)
394 	{
395 		perror("open_logs(): can't open errlog");
396 		return -1;
397 	}
398 	if (setvbuf(errlog, NULL, _IOLBF, 0) == EOF)
399 	{
400 		perror("open_logs(): error changing buffering mode on errlog");
401 		return -1;
402 	}
403 	return 0;
404 }
405 
406 
407 /*
408  * Parse global vars from user's config file
409  *
410  * returns number of servers on success, or -1
411  */
412 int
read_config()413 read_config()
414 {
415 	char cfg[MAXLINE + 1];
416 	char home[PATH_MAX + 1];
417 	char cfgpathname[PATH_MAX + 1];
418 	struct passwd * uinfo;
419 	char * var, * val;
420 	FILE * fp;
421 	int numservers = 0;
422 
423 	/* find the home dir of user running suck */
424 	uinfo = getpwuid(getuid());
425 	strncpy(home, uinfo->pw_dir, PATH_MAX);
426 
427 	/* concatenate $HOME and filename with bounds checking */
428 	snprintf(cfgpathname, PATH_MAX, "%s/%s", home, CONFIGFILENAME);
429 
430 	if ((fp = fopen(cfgpathname, "r")) == NULL)
431 	{
432 		perror("read_config(): can't open configuration file");
433 		return -1;
434 	}
435 
436 	printf("Running as user ID %d (%s) using configuration file %s\n",
437 			uinfo->pw_uid, uinfo->pw_name, cfgpathname);
438 
439 	while (fgets(cfg, MAXLINE, fp) != NULL)
440 	{
441 		if ((var = strtok(cfg, " \t\n")) == NULL)
442 		{
443 			continue;	/* blank line is OK */
444 		}
445 
446 		if ((val = strtok(NULL, " \t\n")) == NULL)	/* blank value is *not*
447 								 * OK */
448 		{
449 			fprintf(stderr, "read_config(): variable/server %s has NULL value!\n", var);
450 			return -1;
451 		}
452 
453 		if (strcasecmp(var, "ERRLOG") == 0)
454 		{
455 			if ((errlogpath = malloc((strlen(val) + 1) * sizeof(char))) == NULL)
456 			{
457 				perror("read_config()");
458 				exit(EXIT_FAILURE);
459 			}
460 			strcpy(errlogpath, val);
461 			continue;
462 		}
463 		if (strcasecmp(var, "KILLLOG") == 0)
464 		{
465 			if ((killlogpath = malloc((strlen(val) + 1) * sizeof(char))) == NULL)
466 			{
467 				perror("read_config()");
468 				exit(EXIT_FAILURE);
469 			}
470 			strcpy(killlogpath, val);
471 			continue;
472 		}
473 		if (strcasecmp(var, "SUCKLOG") == 0)
474 		{
475 			if ((sucklogpath = malloc((strlen(val) + 1) * sizeof(char))) == NULL)
476 			{
477 				perror("read_config()");
478 				exit(EXIT_FAILURE);
479 			}
480 			strcpy(sucklogpath, val);
481 			continue;
482 		}
483 		if (strcasecmp(var, "FILTERS") == 0)
484 		{
485 			if ((filterspath = malloc((strlen(val) + 1) * sizeof(char))) == NULL)
486 			{
487 				perror("read_config()");
488 				exit(EXIT_FAILURE);
489 			}
490 			strcpy(filterspath, val);
491 			continue;
492 		}
493 		if (strcasecmp(var, "NEWSRC_PATH_TEMPLATE") == 0)
494 		{
495 			if ((newsrc_path_template = malloc((strlen(val) + 1) * sizeof(char))) == NULL)
496 			{
497 				perror("read_config()");
498 				exit(EXIT_FAILURE);
499 			}
500 			strcpy(newsrc_path_template, val);
501 			continue;
502 		}
503 
504 		/*
505 		 * Anything else is supposed to be a server definition
506 		 */
507 		if (numservers < MAXNEWSRCS)
508 		{
509 			strncpy(server[numservers].hostname, var, MAXHOSTNAMELEN);
510 			server[numservers++].maxconns = atoi(val);
511 		}
512 		else
513 			fprintf(stderr, "suck: too many server definitions in config, ignoring\n");
514 	}
515 #ifdef DEBUG
516 	printf("errlog: %s\nkilllog: %s\nsucklog: %s\nfilters: %s\nnewsrc_path_template: %s\n", \
517 	       ERRLOG, KILLLOG, SUCKLOG, FILTERS, NEWSRC_PATH_TEMPLATE);
518 #endif
519 
520 	fclose(fp);
521 	return numservers;
522 }
523