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