1 /*
2  *      Copyright (c) 1996-2005 Michael T Pins.  All rights reserved.
3  *
4  * nntp module for nn.
5  *
6  * The original taken from the nntp 1.5 clientlib.c
7  * Modified heavily for nn.
8  *
9  * Rene' Seindal (seindal@diku.dk) Thu Dec  1 18:41:23 1988
10  *
11  * I have modified Rene's code quite a lot for 6.4 -- I hope he
12  * can still recognize a bit here and a byte there; in any case,
13  * any mistakes are mine :-)  ++Kim
14  */
15 
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <unistd.h>
19 #include <stdarg.h>
20 #include <sys/param.h>
21 #include <ctype.h>
22 #include "config.h"
23 #include "global.h"
24 #include "fullname.h"
25 #include "hash.h"
26 #include "hostname.h"
27 #include "libnov.h"
28 #include "nn_term.h"
29 
30 /*
31  *	nn maintains a cache of recently used articles to improve efficiency.
32  *	To change the size of the cache, define NNTPCACHE in config.h to be
33  *	the new size of this cache.
34  */
35 
36 #ifndef NNTPCACHE
37 #define NNTPCACHE	10
38 #endif
39 
40 #ifdef NNTP
41 #include "nntp.h"
42 #include "patchlevel.h"
43 #include <sys/types.h>
44 #include <sys/socket.h>
45 #include <string.h>
46 #include <strings.h>
47 
48 #ifndef EXCELAN
49 #include <netdb.h>
50 #endif
51 
52 #include <errno.h>
53 #include <pwd.h>
54 
55 #ifdef NOV
56 #include "newsoverview.h"
57 #endif
58 
59 /* This is necessary due to the definitions in m-XXX.h */
60 
61 #if !defined(NETWORK_DATABASE) || defined(NETWORK_BYTE_ORDER)
62 #include <netinet/in.h>
63 #endif
64 
65 #ifdef EXCELAN
66 
67 #ifndef IPPORT_NNTP
68 #define	IPPORT_NNTP	119
69 #endif
70 
71 #endif
72 
73 /* nntp.c */
74 
75 static int      reconnect_server(int);
76 static int      connect_server(void);
77 static void     debug_msg(char *prefix, char *str);
78 static void     find_server(void);
79 static char    *find_domain(const char *domainFile);
80 static int      get_server_line(char *string, int size);
81 static int      get_server(char *string, int size);
82 static int      get_socket(void);
83 static void     put_server(char *string);
84 static int      copy_text(register FILE * fp);
85 static struct cache *search_cache(article_number art, group_header * gh);
86 static struct cache *new_cache_slot(void);
87 static void     clean_cache(void);
88 static void     set_domain(void);
89 static void     nntp_doauth(void);
90 
91 #ifndef NOV
92 static int      sort_art_list(register article_number * f1, register article_number * f2);
93 #endif				/* !NOV */
94 
95 #ifdef NeXT
96 static char    *strdup(char *);
97 #endif				/* NeXT */
98 
99 extern char    *db_directory, *tmp_directory, *news_active;
100 extern char     delayed_msg[];
101 
102 static char     host_name[MAXHOSTNAMELEN];
103 char            domain[MAXHOSTNAMELEN];
104 static char     last_put[NNTP_STRLEN];
105 
106 int             nntp_failed = 0;/* bool: t iff connection is broken in
107 				 * nntp_get_article() or nntp_get_active() */
108 
109 int             nntp_cache_size = NNTPCACHE;
110 char           *nntp_cache_dir = NULL;
111 char           *nntp_server = NNTP_SERVER;	/* name of nntp server */
112 char           *nntp_user, *nntp_password;	/* so can set on command line */
113 
114 int             nntp_local_server = 0;
115 int             nntp_debug = 0;
116 
117 extern char    *home_directory;
118 extern int      silent;
119 
120 extern char    *mktemp();
121 
122 static FILE    *nntp_in = NULL;	/* fp for reading from server */
123 static FILE    *nntp_out = NULL;/* fp for writing to server */
124 static int      reconnecting = 0;
125 static int      is_connected = 0;	/* bool: t iff we are connected */
126 static int      need_auth = 0;
127 static group_header *group_hd;	/* ptr to servers current group */
128 static int      can_post = 0;	/* bool: t iff NNTP server accepts postings */
129 
130 #define ERR_TIMEOUT	503	/* Response code for timeout */
131  /* Same value as ERR_FAULT */
132 
133 #ifdef NO_RENAME
134 static int
rename(char * old,char * new)135 rename(char *old, char *new)
136 {
137     if (unlink(new) < 0 && errno != ENOENT)
138 	return -1;
139     if (link(old, new) < 0)
140 	return -1;
141     return unlink(old);
142 }
143 
144 #endif				/* NO_RENAME */
145 
146 /*
147  * debug_msg: print a debug message.
148  *
149  *	The master appends prefix and str to a log file, and clients
150  *	prints it as a message.
151  *
152  *	This is controlled via the nntp-debug variable in nn, and
153  *	the option -D2 (or -D3 if the normal -D option should also
154  *	be turned on).	Debug output from the master is written in
155  *	$TMP/nnmaster.log.
156  */
157 
158 static void
debug_msg(char * prefix,char * str)159 debug_msg(char *prefix, char *str)
160 {
161     static FILE    *f = NULL;
162 
163     if (who_am_i == I_AM_MASTER) {
164 	if (f == NULL) {
165 	    f = open_file(relative(tmp_directory, "nnmaster.log"), OPEN_CREATE);
166 	    if (f == NULL) {
167 		nntp_debug = 0;
168 		return;
169 	    }
170 	}
171 	fprintf(f, "%s %s\n", prefix, str);
172 	fflush(f);
173 	return;
174     }
175     msg("NNTP%s %s", prefix, str);
176     user_delay(3);
177 }
178 
179 
180 /*
181  * find_server: Find out which host to use as NNTP server.
182  *
183  *	This is done by consulting the file NNTP_SERVER (defined in
184  *	config.h).  Set nntp_server[] to the host's name.
185  */
186 
187 static void
find_server(void)188 find_server(void)
189 {
190     char           *cp, *name;
191     char            buf[BUFSIZ];
192     FILE           *fp;
193 
194     /*
195      * This feature cannot normally be enabled, because the database and the
196      * users rc file contains references to articles by number, and these
197      * numbers are not unique across NNTP servers.
198      */
199 
200 #ifdef NOV
201 
202     /*
203      * But, since we no longer have a database to worry about, give the user
204      * the rope. If he wants to hang himself, then let him! :-) Let the user
205      * worry about keeping his .newsrc straight.
206      */
207     if ((cp = getenv("NNTPSERVER")) != NULL) {
208 	nntp_server = cp;
209 	return;
210     } else if (*nntp_server != '/')
211 	return;			/* variable was set on cmd line, or in init
212 				 * file */
213 #endif				/* NOV */
214 
215     name = nntp_server;		/* default, or variable was set to a filename */
216 
217     if ((fp = open_file(name, OPEN_READ)) != NULL) {
218 	while (fgets(buf, sizeof buf, fp) != 0) {
219 	    if (*buf == '#' || *buf == '\n')
220 		continue;
221 	    if ((cp = strchr(buf, '\n')) != 0)
222 		*cp = '\0';
223 	    nntp_server = strdup(buf);
224 	    if (!nntp_server)
225 		sys_error("Failed to allocate space for name of NNTP server!");
226 	    fclose(fp);
227 	    return;
228 	}
229 	fclose(fp);
230     }
231     if (who_am_i != I_AM_MASTER)
232 	printf("\nCannot find name of NNTP server.\nCheck %s\n", name);
233 
234     sys_error("Failed to find name of NNTP server!");
235 }
236 
237 
238 /*
239  * find_domain		Get the domain name for posting from a named file.
240  *			Handle blank lines and comments.
241  *
242  *	Parameters:	"file" is the name of the file to read.
243  *
244  *	Returns:	Pointer to static data area containing the
245  *			first non-blank/comment line in the file.
246  *			NULL on error (or lack of entry in file).
247  *
248  *	Side effects:	None.
249  */
250 
251 static char *
find_domain(const char * domainFile)252 find_domain(const char *domainFile)
253 {
254     register FILE  *fp;
255     register char  *cp;
256     static char     buf[MAXHOSTNAMELEN];
257     char           *index();
258 
259     if (domainFile == NULL)
260 	return (NULL);
261 
262     fp = fopen(domainFile, "r");
263     if (fp == NULL)
264 	return (NULL);
265 
266     while (fgets(buf, sizeof (buf), fp) != NULL) {
267 	if (*buf == '\n' || *buf == '#')
268 	    continue;
269 	cp = index(buf, '\n');
270 	if (cp)
271 	    *cp = '\0';
272 	(void) fclose(fp);
273 	return (buf);
274     }
275 
276     (void) fclose(fp);
277     return (NULL);
278 }
279 
280 
281 /*
282  * get_server_line: get a line from the server.
283  *
284  *	Expects to be connected to the server.
285  *	The line can be any kind of line, i.e., either response or text.
286  *	Returns length of line if no error.
287  *	If error and master, then return -1, else terminate.
288  */
289 
290 static int
get_server_line(register char * string,register int size)291 get_server_line(register char *string, register int size)
292 {
293     int             retry = 2;
294 
295     while (fgets(string, size, nntp_in) == NULL)
296 	retry = (reconnect_server(retry));
297 
298     size = strlen(string);
299     if (size < 2 || !(string[size - 2] == '\r' && string[size - 1] == '\n'))
300 	return size;		/* XXX */
301 
302     string[size - 2] = '\0';	/* nuke CRLF */
303     return size - 2;
304 }
305 
306 
307 /*
308  * get_server: get a response line from the server.
309  *
310  *	Expects to be connected to the server.
311  *	Returns the numerical value of the reponse, or -1 in case of errors.
312  */
313 
314 static int
get_server(char * string,int size)315 get_server(char *string, int size)
316 {
317     if (get_server_line(string, size) < 0)
318 	return -1;
319 
320     if (nntp_debug)
321 	debug_msg("<<<", string);
322 
323     return isdigit(*string) ? atoi(string) : 0;
324 }
325 
326 /*
327  * get_socket:	get a connection to the nntp server.
328  *
329  * Errors can happen when YP services or DNS are temporarily down or
330  * hung, so we log errors and return failure rather than exitting if we
331  * are the master.  The effects of retrying every 15 minutes (or whatever
332  * the -r interval is) are not that bad.  Dave Olson, SGI
333  */
334 
335 static int
get_socket(void)336 get_socket(void)
337 {
338     int             s;
339     struct sockaddr_in sin;
340 
341 #ifndef EXCELAN
342     struct servent *sp;
343     struct hostent *hp;
344 
345 #ifdef h_addr
346     int             x = 0;
347     register char **cp;
348 #endif				/* h_addr */
349 
350     if ((sp = getservbyname("nntp", "tcp")) == NULL)
351 	return sys_warning("nntp/tcp: Unknown service.\n");
352 
353     s = who_am_i == I_AM_MASTER ? 10 : 2;
354     while ((hp = gethostbyname(nntp_server)) == NULL) {
355 	if (--s < 0)
356 	    goto host_err;
357 	sleep(10);
358     }
359 
360     clearobj(&sin, sizeof(sin), 1);
361     sin.sin_family = hp->h_addrtype;
362     sin.sin_port = sp->s_port;
363 
364 #else				/* EXCELAN */
365     char           *machine;
366 
367     clearobj(&sin, sizeof(sin), 1);
368     sin.sin_family = AF_INET;
369     sin.sin_port = htons(0);
370 #endif				/* EXCELAN */
371 
372 #ifdef h_addr
373     /* get a socket and initiate connection -- use multiple addresses */
374 
375     s = x = -1;
376     for (cp = hp->h_addr_list; cp && *cp; cp++) {
377 	s = socket(hp->h_addrtype, SOCK_STREAM, 0);
378 	if (s < 0)
379 	    goto sock_err;
380 
381 #ifdef NO_MEMMOVE
382 	bcopy(*cp, (char *) &sin.sin_addr, hp->h_length);
383 #else
384 	memmove((char *) &sin.sin_addr, *cp, hp->h_length);
385 #endif				/* NO_MEMMOVE */
386 
387 	/* Quick hack to work around interrupting system calls.. */
388 	while ((x = connect(s, (struct sockaddr *) & sin, sizeof(sin))) < 0 &&
389 	       errno == EINTR)
390 	    sleep(1);
391 	if (x == 0)
392 	    break;
393 	if (who_am_i != I_AM_MASTER)
394 	    msg("Connecting to %s failed: %s", nntp_server, strerror(errno));
395 	(void) close(s);
396 	s = -1;
397     }
398     if (x < 0)
399 	sys_warning("Giving up on NNTP server %s!", nntp_server);
400 #else	/* h_addr */		/* no name server */
401 
402 #ifdef EXCELAN
403     if ((s = socket(SOCK_STREAM, NULL, &sin, SO_KEEPALIVE)) < 0)
404 	goto sock_err;
405 
406     sin.sin_port = htons(IPPORT_NNTP);
407     machine = nntp_server;
408     if ((sin.sin_addr.s_addr = rhost(&machine)) == -1) {
409 	(void) close(s);
410 	goto host_err;
411     }
412     /* And then connect */
413     if (connect(s, &sin) < 0)
414 	goto conn_err;
415 #else				/* not EXCELAN */
416     if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)
417 	goto sock_err;
418 
419     /* And then connect */
420 
421 #ifdef NO_MEMMOVE
422     bcopy(hp->h_addr, (char *) &sin.sin_addr, hp->h_length);
423 #else
424     memmove((char *) &sin.sin_addr, hp->h_addr, hp->h_length);
425 #endif				/* NO_MEMMOVE */
426 
427     if (connect(s, (struct sockaddr *) & sin, sizeof(sin)) < 0)
428 	goto conn_err;
429 #endif				/* EXCELAN */
430 
431 #endif				/* h_addr */
432 
433     return s;
434 
435 host_err:
436     sys_warning("NNTP server %s unknown.\n", nntp_server);
437     return -1;
438 
439 sock_err:
440     sys_warning("Can't get NNTP socket: %s", strerror(errno));
441     return -1;
442 
443 #ifndef h_addr
444 conn_err:
445     (void) close(s);
446     if (who_am_i == I_AM_MASTER)
447 	sys_warning("Connecting to %s failed: %s", nntp_server, strerror(errno));
448     return -1;
449 #endif
450 
451 }
452 
453 
454 /*
455  * connect_server: initialise a connection to the nntp server.
456  *
457  *	It expects nntp_server[] to be set previously, by a call to
458  *	nntp_check.  It is called from nntp_get_article() and
459  *	nntp_get_active() if there is no established connection.
460  *	This gets called at initialization, and whenever it looks
461  *	like the server may have closed the connection on us.
462  */
463 
464 static int
connect_server(void)465 connect_server(void)
466 {
467     int             sockt_rd, sockt_wr;
468     int             response;
469     int             triedauth = 0;
470     char            line[NNTP_STRLEN];
471 
472     if (who_am_i == I_AM_VIEW)
473 	nntp_check();
474 
475     if (who_am_i != I_AM_MASTER && !silent && !reconnecting)
476 	msg("Connecting to NNTP server %s ...", nntp_server);
477 
478     nntp_failed = 1;
479     is_connected = 0;
480 
481     sockt_rd = get_socket();
482     if (sockt_rd < 0)
483 	return -1;
484 
485     if ((nntp_in = fdopen(sockt_rd, "r")) == NULL) {
486 	close(sockt_rd);
487 	return -1;
488     }
489     sockt_wr = dup(sockt_rd);
490     if ((nntp_out = fdopen(sockt_wr, "w")) == NULL) {
491 	close(sockt_wr);
492 	fclose(nntp_in);
493 	nntp_in = NULL;		/* from above */
494 	return -1;
495     }
496     /* Now get the server's signon message */
497     response = get_server(line, sizeof(line));
498 
499     if (who_am_i == I_AM_MASTER) {
500 	if (response != OK_CANPOST && response != OK_NOPOST) {
501 	    log_entry('N', "Failed to connect to NNTP server");
502 	    log_entry('N', "Response: %s", line);
503 	    fclose(nntp_out);
504 	    fclose(nntp_in);
505 	    return -1;
506 	}
507     } else if (reconnecting && need_auth) {
508 	nntp_doauth();
509 	triedauth++;
510     } else {
511 	switch (response) {
512 	    case OK_AUTH:
513 		break;
514 	    case OK_CANPOST:
515 		can_post = 1;
516 		break;
517 	    case OK_NOPOST:
518 		can_post = 0;
519 		break;
520 	    default:
521 		nn_exitmsg(1, "%s", line);
522 		/* NOTREACHED */
523 	}
524     }
525 
526 retrymode:
527     /* Try and speak NNRP style reader protocol */
528     sprintf(line, "MODE READER");
529     put_server(line);
530     /* See what we got if from a NNRP compiant server like INN's nnrpd */
531     response = get_server(line, sizeof(line));
532 
533     switch (response) {
534 	case OK_CANPOST:
535 	    can_post = 1;
536 	    break;
537 	case OK_NOPOST:
538 	    can_post = 0;
539 	    break;
540 	case ERR_NEEDAUTH:
541 	    if (!triedauth) {
542 		nntp_doauth();
543 		triedauth++;
544 		goto retrymode;
545 	    }
546 	    break;		/* will probably fail hard later */
547 	case ERR_COMMAND:
548 	default:
549 	    /* if it doesn't understand MODE READER, we dont care.. :-) */
550 	    break;
551     }
552 
553     if (!can_post && !triedauth) {
554 
555 	/*
556 	 * sometimes servers say no posting until auth done, so try. it might
557 	 * be better if this was controlled by some variable, since some
558 	 * people are OK with readonly access
559 	 */
560 	msg("No post, so trying authorization; if readonly OK, just skip");
561 	nntp_doauth();
562 	triedauth++;
563 	goto retrymode;
564     }
565     if (who_am_i != I_AM_MASTER && !silent && !reconnecting)
566 	msg("Connecting to NNTP server %s ... ok (%s)",
567 	    nntp_server, can_post ? "posting is allowed" : "no posting");
568 
569     is_connected = 1;
570     nntp_failed = 0;
571     return 0;
572 }
573 
574 
575 /*
576  * reconnect_server -- reconnect to server after a timeout.
577  * Re-establish correct group and resend last command to get back to the
578  * correct state.
579  *
580  *	Returns:	-1 if the error is fatal (and we should exit).
581  *			# of retries left otherwise.
582  *
583  *	Side Effects:	If fails to connect and retry==0, calls nn_exitmsg.
584  */
585 
586 static int
reconnect_server(int retry)587 reconnect_server(int retry)
588 {
589     char            buf[NNTP_STRLEN];
590 
591     retry--;
592     msg("Reconnecting");
593     if (nntp_debug) {
594 	msg("retry = %d", retry);
595 	user_delay(2);
596     }
597     reconnecting = 1;
598     strcpy(buf, last_put);
599     nntp_close_server();
600     if (connect_server() < 0) {
601 	if (nntp_debug)
602 	    debug_msg("failed to connect", "");
603 	if (retry > 0)
604 	    return (retry);
605 	else
606 	    nn_exitmsg(1, "failed to reconnect to server");
607     }
608     if (group_hd)
609 	if (nntp_set_group(group_hd) < 1)
610 	    nn_exitmsg(1, "unable to set group");
611 
612     if (nntp_debug)
613 	debug_msg(" Resend last command", buf);
614     put_server(buf);
615     reconnecting = 0;
616     return (retry);
617 }
618 
619 
620 /*
621  * put_server:	send a line to the nntp server.
622  *
623  *	Expects to be connected to the server.
624  */
625 
626 static void
put_server(char * string)627 put_server(char *string)
628 {
629     if (nntp_debug)
630 	debug_msg(">>>", string);
631 
632     strcpy(last_put, string);
633     fprintf(nntp_out, "%s\r\n", string);
634     (void) fflush(nntp_out);
635     return;
636 }
637 
638 /*
639  * ask_server:	ask the server a question and return the answer.
640  *
641  *	Expects to be connected to the server.
642  *	Returns the numerical value of the reponse, or -1 in case of
643  *	errors.
644  *	Contains some code to handle server timeouts intelligently.
645  */
646 
647 /* LIST XXX returns fatal ERR_FAULT code if requested list does not exist */
648 /* This is only fatal for LIST ACTIVE -- else change to ERR_NOGROUPS */
649 static int      fix_list_response = 0;
650 static char     ask_reply[NNTP_STRLEN];
651 
652 static int
ask_server(char * fmt,...)653 ask_server(char *fmt,...)
654 {
655     char            ask[NNTP_STRLEN];
656     int             response;
657     int             fix_err;
658     va_list         ap;
659 
660     fix_err = fix_list_response;
661     fix_list_response = 0;
662 
663     va_start(ap, fmt);
664     vsprintf(ask, fmt, ap);
665     va_end(ap);
666 
667     put_server(ask);
668     response = get_server(ask_reply, sizeof(ask_reply));
669 
670     /*
671      * Handle the response from the server.  Responses are handled as
672      * followes:
673      *
674      * 100-199	Informational.	Passed back. (should they be ignored?).
675      * 200-299	Ok messages.  Passed back. 300-399	Ok and proceed.  Can
676      * not happen in nn. 400-499	Errors (no article, etc).  Passed up
677      * and handled there. 480 (NEED_AUTH) handle specially.  Done here
678      * because some servers can send it at any point, after an idle period,
679      * as well as at various points during connect. 500-599	Fatal NNTP
680      * errors.  Handled below.
681      */
682 
683     if (response == ERR_TIMEOUT) {
684 	(void) reconnect_server(1);
685 	response = get_server(ask_reply, sizeof(ask_reply));
686     }
687     if (response == ERR_NEEDAUTH) {
688 
689 	/*
690 	 * try auth, but just once, at least for now, and then retry the
691 	 * original command, and continue
692 	 */
693 	nntp_doauth();
694 	put_server(ask);
695 	response = get_server(ask_reply, sizeof(ask_reply));
696     }
697     if (response == ERR_GOODBYE || response > ERR_COMMAND) {
698 	if (fix_err && (response == ERR_FAULT || response == ERR_CMDSYN))
699 	    return ERR_NOGROUP;
700 
701 	nntp_failed = 1;
702 	nntp_close_server();
703 
704 	if (response != ERR_GOODBYE) {
705 	    /* if not goodbye, complain */
706 	    sys_error("NNTP %s response: %d", ask_reply, response);
707 	    /* NOTREACHED */
708 	}
709 	if (response == ERR_GOODBYE) {
710 	    sys_warning("NNTP %s response: %d", ask_reply, response);
711 	}
712     }
713     return response;
714 }
715 
716 /*
717  * copy_text: copy text response into file.
718  *
719  *	Copies a text response into an open file.
720  *	Return -1 on error, 0 otherwise.  It is treated as an error, if
721  *	the returned response it not what was expected.
722  */
723 
724 static int      last_copy_blank;
725 
726 static int
copy_text(register FILE * fp)727 copy_text(register FILE * fp)
728 {
729     char            buf[NNTP_STRLEN * 2];
730     register char  *cp;
731     register int    nlines;
732 
733     nlines = 0;
734     last_copy_blank = 0;
735     while (get_server_line(buf, sizeof buf) >= 0) {
736 	cp = buf;
737 	if (*cp == '.')
738 	    if (*++cp == '\0') {
739 		if (nlines <= 0)
740 		    break;
741 		if (nntp_debug) {
742 		    sprintf(buf, "%d lines", nlines);
743 		    debug_msg("COPY", buf);
744 		}
745 		return 0;
746 	    }
747 	last_copy_blank = (*cp == NUL);
748 	fputs(cp, fp);
749 	putc('\n', fp);
750 	nlines++;
751     }
752     fclose(fp);
753     if (nntp_debug)
754 	debug_msg("COPY", "EMPTY");
755     return -1;
756 }
757 
758 
759 /*
760  * The following functions implements a simple lru cache of recently
761  * accessed articles.  It is a simple way to improve effeciency.  Files
762  * must be kept by name, because the rest of the code expects to be able
763  * to open an article multiple times, and get separate file pointers.
764  */
765 
766 struct cache {
767     char           *file_name;	/* file name */
768     article_number  art;	/* article stored in file */
769     group_header   *grp;	/* from this group */
770     unsigned        time;	/* time last accessed */
771 }               cache[NNTPCACHE];
772 
773 static unsigned time_counter = 1;	/* virtual time */
774 
775 /*
776  * search_cache: search the cache for an (article, group) pair.
777  *
778  *	Returns a pointer to the slot where it is, null otherwise
779  */
780 
781 static struct cache *
search_cache(article_number art,group_header * gh)782 search_cache(article_number art, group_header * gh)
783 {
784     struct cache   *cptr = cache;
785     int             i;
786 
787     if (who_am_i == I_AM_MASTER)
788 	return NULL;
789 
790     if (nntp_cache_size > NNTPCACHE)
791 	nntp_cache_size = NNTPCACHE;
792 
793     for (i = 0; i < nntp_cache_size; i++, cptr++)
794 	if (cptr->art == art && cptr->grp == gh) {
795 	    cptr->time = time_counter++;
796 	    return cptr;
797 	}
798     return NULL;
799 }
800 
801 /*
802  * new_cache_slot: get a free cache slot.
803  *
804  *	Returns a pointer to the allocated slot.
805  *	Frees the old filename, and allocates a new, unused filename.
806  *	Cache files can also stored in a common directory defined in
807  *	~/.nn or CACHE_DIRECTORY if defined in config.h.
808  */
809 
810 static struct cache *
new_cache_slot(void)811 new_cache_slot(void)
812 {
813     register struct cache *cptr = cache;
814     int             i, lru = 0;
815     unsigned        min_time = time_counter;
816     char            name[FILENAME];
817 
818     if (nntp_cache_dir == NULL) {
819 
820 #ifdef CACHE_DIRECTORY
821 	nntp_cache_dir = CACHE_DIRECTORY;
822 #else				/* CACHE_DIRECTORY */
823 	if (who_am_i == I_AM_MASTER)
824 	    nntp_cache_dir = db_directory;
825 	else
826 	    nntp_cache_dir = nn_directory;
827 #endif				/* CACHE_DIRECTORY */
828     }
829     if (who_am_i == I_AM_MASTER) {
830 	cptr = &cache[0];
831 	if (cptr->file_name == NULL)
832 	    cptr->file_name = mk_file_name(nntp_cache_dir, "master_cache");
833 	return cptr;
834     }
835     for (i = 0; i < nntp_cache_size; i++, cptr++)
836 	if (min_time > cptr->time) {
837 	    min_time = cptr->time;
838 	    lru = i;
839 	}
840     cptr = &cache[lru];
841 
842     if (cptr->file_name == NULL) {
843 	sprintf(name, "%s/nn-%d.%02d~", nntp_cache_dir, process_id, lru);
844 	cptr->file_name = copy_str(name);
845     } else
846 	unlink(cptr->file_name);
847 
848     cptr->time = time_counter++;
849     return cptr;
850 }
851 
852 /*
853  * clean_cache: clean up the cache.
854  *
855  *	Removes all allocated files.
856  */
857 
858 static void
clean_cache(void)859 clean_cache(void)
860 {
861     struct cache   *cptr = cache;
862     int             i;
863 
864     for (i = 0; i < nntp_cache_size; i++, cptr++)
865 	if (cptr->file_name)
866 	    unlink(cptr->file_name);
867 }
868 
869 /*
870  * set_domain: set the domain name
871  */
872 
873 static void
set_domain(void)874 set_domain(void)
875 {
876 
877 #if !defined(DOMAIN) || !defined(HIDDENNET)
878     char           *cp;
879 #endif
880 
881 #ifdef DOMAIN
882 
883 #ifdef HIDDENNET
884     strncpy(domain, DOMAIN, MAXHOSTNAMELEN);
885 #else
886     strcpy(domain, host_name);
887     cp = index(domain, '.');
888     if (cp == NULL) {
889 	strcat(domain, ".");
890 	strncat(domain, DOMAIN, MAXHOSTNAMELEN - sizeof(host_name) - 1);
891     }
892 #endif				/* HIDDENNET */
893 
894 #else				/* DOMAIN */
895 
896     /*
897      * if domain is defined in DOMAIN_FILE, use it
898      */
899     cp = find_domain(DOMAIN_FILE);
900     if (cp) {
901 	strncpy(domain, cp, MAXHOSTNAMELEN);
902 	domain[MAXHOSTNAMELEN-1] = 0;	/* ensure nul-terminated */
903 	return;
904     }
905 
906     domain[0] = '\0';
907 
908     cp = index(host_name, '.');
909     if (cp == NULL) {
910 	FILE           *resolv;
911 
912 	if ((resolv = fopen("/etc/resolv.conf", "r")) != NULL) {
913 	    char            line[MAXHOSTNAMELEN + 1];
914 	    char           *p;
915 
916 	    line[MAXHOSTNAMELEN] = '\0';
917 	    while (fgets(line, MAXHOSTNAMELEN, resolv)) {
918 		if (strncmp(line, "domain", 6) == 0) {
919 		    for (p = &line[6]; *p && isspace(*p); p++);
920 		    (void) strncpy(domain, p, MAXHOSTNAMELEN);
921 		    p = domain + strlen(domain) - 1;
922 		    while ((p >= domain) && (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n'))
923 			p--;
924 		    *++p = '\0';
925 		    break;
926 		}
927 	    }
928 	}
929 	if (domain[0] == '\0')
930 	    nn_exitmsg(1, "hostname=%s, You need a fully qualified domain name", host_name);
931     } else {
932 
933 #ifdef HIDDENNET
934 	strncpy(domain, ++cp, MAXHOSTNAMELEN);
935 #else
936 	strncpy(domain, host_name, MAXHOSTNAMELEN);
937 #endif
938     }
939 #endif				/* DOMAIN */
940     domain[MAXHOSTNAMELEN-1] = 0;	/* ensure nul-terminated */
941 }
942 
943 /*
944  * nntp_check: Find out whether we need to use NNTP.
945  *
946  *	This is done by comparing the NNTP servers name with whatever
947  *	nn_gethostname() returns.
948  *	use_nntp and news_active are initialised as a side effect.
949  */
950 
951 void
nntp_check(void)952 nntp_check(void)
953 {
954     char           *server_real_name = NULL;
955     struct hostent *hp;
956 
957     nn_gethostname(host_name, sizeof host_name);
958     set_domain();
959 
960     last_put[0] = '\0';
961 
962     if (nntp_local_server)
963 	return;
964 
965     find_server();
966 
967     if ((hp = gethostbyname(host_name)) != NULL)
968 	strncpy(host_name, hp->h_name, sizeof host_name);
969 
970     if ((hp = gethostbyname(nntp_server)) != NULL)
971 	server_real_name = hp->h_name;
972     else
973 	nn_exitmsg(1, "NNTPSERVER is invalid");
974     use_nntp = (strcmp(host_name, server_real_name) != 0);
975 
976     if (use_nntp) {
977 	freeobj(news_active);
978 
979 #ifndef NOV
980 	news_active = mk_file_name(db_directory, "ACTIVE");
981 #else				/* NOV */
982 	news_active = mk_file_name(nn_directory, "ACTIVE");
983 #endif				/* NOV */
984     }
985 }
986 
987 
988 /*
989  * nntp_set_group -- set the server's current group.
990  *
991  *      Parameters:     gh is the structure containing the group name.
992  *
993  *      Returns:         1 if OK.
994  *                      -1 if no such group.
995  *                       0 otherwise.
996  */
997 
998 int
nntp_set_group(group_header * gh)999 nntp_set_group(group_header * gh)
1000 {
1001     int             n;
1002 
1003     switch (n = ask_server("GROUP %s", gh->group_name)) {
1004 	case OK_GROUP:
1005 	    group_hd = gh;
1006 	    return 1;
1007 
1008 	case ERR_NOGROUP:
1009 	    log_entry('N', "NNTP: group %s not found\n", gh->group_name);
1010 	    return -1;
1011 
1012 	default:		/* ??? returns this if connection down */
1013 	    log_entry('N', "GROUP %s response: %d", group_hd->group_name, n);
1014 	    nntp_failed = 1;
1015 	    return -1;		/* What did it return? */
1016     }
1017 }
1018 
1019 
1020 #ifndef NOV
1021 /*
1022  * nntp_get_active:  get a copy of the active file.
1023  *
1024  *	If we are the master get a copy of the file from the nntp server.
1025  *	nnadmin just uses the one we already got.  In this way the master
1026  *	can maintain a remote copy of the servers active file.
1027  *	We try to be a little smart, if not inefficient, about the
1028  *	modification times on the local active file.
1029  *	Even when the master is running on the nntp server, a separate
1030  *	copy of the active file will be made for access via NFS.
1031  */
1032 
1033 int
nntp_get_active(void)1034 nntp_get_active(void)
1035 {
1036     FILE           *old, *new;
1037     char            bufo[NNTP_STRLEN], bufn[NNTP_STRLEN];
1038     char           *new_name;
1039     int             same, n;
1040 
1041     if (who_am_i != I_AM_MASTER)
1042 	return access(news_active, 4);
1043 
1044     if (!is_connected && connect_server() < 0)
1045 	return -1;
1046 
1047     new_name = mktemp(relative(db_directory, ".actXXXXXX"));
1048 
1049     switch (n = ask_server("LIST")) {
1050 	case OK_GROUPS:
1051 	    new = open_file(new_name, OPEN_CREATE_RW | MUST_EXIST);
1052 	    if (copy_text(new) == 0) {
1053 		if (fflush(new) != EOF)
1054 		    break;
1055 		fclose(new);
1056 	    }
1057 	    unlink(new_name);
1058 	    if (!nntp_failed) {
1059 		log_entry('N', "LIST empty");
1060 		nntp_failed = 1;
1061 	    }
1062 	    return -1;
1063 	default:
1064 	    log_entry('N', "LIST response: %d", n);
1065 	    return -1;
1066     }
1067 
1068     rewind(new);
1069     same = 0;
1070     if ((old = open_file(news_active, OPEN_READ)) != NULL) {
1071 	do {
1072 	    fgets(bufo, sizeof bufo, old);
1073 	    fgets(bufn, sizeof bufn, new);
1074 	} while (!feof(old) && !feof(new) && strcmp(bufo, bufn) == 0);
1075 	same = feof(old) && feof(new);
1076 	fclose(old);
1077     }
1078     fclose(new);
1079 
1080     if (same)
1081 	unlink(new_name);
1082     else if (rename(new_name, news_active) != 0)
1083 	sys_error("Cannot rename %s to %s", new_name, news_active);
1084 
1085     return 0;
1086 }
1087 
1088 #endif				/* !NOV */
1089 
1090 /*
1091  * nntp_get_newsgroups:  get a copy of the newsgroups file.
1092  *
1093  *	Use the "LIST NEWSGROUPS" command to get the newsgroup descriptions.
1094  *	Based on code from: olson%anchor.esd@sgi.com (Dave Olson)
1095  */
1096 
1097 FILE           *
nntp_get_newsgroups(void)1098 nntp_get_newsgroups(void)
1099 {
1100     char           *new_name;
1101     FILE           *new;
1102     int             n;
1103 
1104     new_name = mktemp(relative(tmp_directory, "nngrXXXXXX"));
1105     new = open_file(new_name, OPEN_CREATE_RW | OPEN_UNLINK);
1106     if (new == NULL)
1107 	return NULL;
1108 
1109     if (!is_connected && connect_server() < 0)
1110 	goto err;
1111 
1112     fix_list_response = 1;
1113     switch (n = ask_server("LIST NEWSGROUPS")) {
1114 	case ERR_NOGROUP:	/* really ERR_FAULT */
1115 	    goto err;
1116 
1117 	case OK_GROUPS:
1118 	    if (copy_text(new) == 0) {
1119 		if (fflush(new) != EOF)
1120 		    break;
1121 		fclose(new);
1122 	    }
1123 	    if (!nntp_failed) {
1124 		log_entry('N', "LIST NEWSGROUPS empty");
1125 		nntp_failed = 1;
1126 	    }
1127 	    return NULL;
1128 
1129 	default:
1130 	    log_entry('N', "LIST NEWSGROUPS response: %d", n);
1131 	    goto err;
1132     }
1133     rewind(new);
1134     return new;
1135 
1136 err:
1137     fclose(new);
1138     return NULL;
1139 }
1140 
1141 #ifndef NOV
1142 /*
1143  * nntp_get_article_list: get list of all article numbers in group
1144  *
1145  *	Sends XHDR command to the server, and parses the following
1146  *	text response to get a list of article numbers which is saved
1147  *	in a list and returned.
1148  *	Return NULL on error.  It is treated as an error, if
1149  *	the returned response it not what was expected.
1150  */
1151 
1152 static article_number *article_list = NULL;
1153 static long     art_list_length = 0;
1154 
1155 static int
sort_art_list(register article_number * f1,register article_number * f2)1156 sort_art_list(register article_number * f1, register article_number * f2)
1157 {
1158     return (*f1 < *f2) ? -1 : (*f1 == *f2) ? 0 : 1;
1159 }
1160 
1161 article_number *
nntp_get_article_list(group_header * gh)1162 nntp_get_article_list(group_header * gh)
1163 {
1164     char            buf[NNTP_STRLEN];
1165     register article_number *art;
1166     register char  *cp;
1167     register long   count = 0;	/* No. of completions plus one */
1168     int             n;
1169     static int      try_listgroup = 1;
1170 
1171     if (!is_connected && connect_server() < 0)
1172 	return NULL;
1173 
1174     /* it is really an extreme waste of time to use XHDR since all we	 */
1175     /* are interested in is the article numbers (as we do locally).	 */
1176     /* If somebody hacks up an nntp server that understands LISTGROUP	 */
1177     /* they will get much less load on the nntp server			 */
1178     /* It should simply return the existing article numbers is the group */
1179     /* -- they don't even have to be sorted (only XHDR needs that)	 */
1180 
1181     if (try_listgroup) {
1182 	switch (n = ask_server("LISTGROUP %s", group_hd->group_name)) {
1183 	    case OK_GROUP:
1184 		break;
1185 	    default:
1186 		log_entry('N', "LISTGROUP response: %d", n);
1187 		return NULL;
1188 	    case ERR_COMMAND:
1189 		try_listgroup = 0;
1190 	}
1191     }
1192     if (!try_listgroup) {
1193 	switch (n = ask_server("XHDR message-id %ld-%ld",
1194 		 (long) gh->first_db_article, (long) gh->last_db_article)) {
1195 	    case OK_HEAD:
1196 		break;
1197 	    default:
1198 		log_entry('N', "XHDR response: %d", n);
1199 		return NULL;
1200 	    case ERR_COMMAND:
1201 		nntp_failed = 2;
1202 		return NULL;
1203 	}
1204     }
1205     count = 0;
1206     art = article_list;
1207 
1208     while (get_server_line(buf, sizeof buf) >= 0) {
1209 	cp = buf;
1210 	if (*cp == '.' && *++cp == '\0')
1211 	    break;
1212 
1213 	if (count == art_list_length) {
1214 	    art_list_length += 250;
1215 	    article_list = resizeobj(article_list, article_number, art_list_length + 1);
1216 	    art = article_list + count;
1217 	}
1218 	*art++ = atol(cp);
1219 	count++;
1220     }
1221 
1222     if (article_list != NULL) {
1223 	*art = 0;
1224 	if (try_listgroup && count > 1)
1225 	    quicksort(article_list, count, article_number, sort_art_list);
1226     }
1227     return article_list;
1228 }
1229 
1230 #endif				/* NOV */
1231 
1232 /*
1233  * nntp_get_article: get an article from the server.
1234  *
1235  *	Returns a FILE pointer.
1236  *	If necessary the server's current group is set.
1237  *	The article (header and body) are copied into a file, so they
1238  *	are seekable (nn likes that).
1239  */
1240 
1241 static char    *mode_cmd[] = {
1242     "ARTICLE",
1243     "HEAD",
1244     "BODY"
1245 };
1246 
1247 FILE           *
nntp_get_article(article_number article,int mode)1248 nntp_get_article(article_number article, int mode)
1249  /* mode: 0 => whole article, 1 => head only, 2 => body only */
1250 {
1251     FILE           *tmp;
1252     static struct cache *cptr;
1253     int             n;
1254 
1255     if (!is_connected && connect_server() < 0) {
1256 	return NULL;
1257     }
1258 
1259     /*
1260      * Search the cache for the requested article, and allocate a new slot if
1261      * necessary (if appending body, we already got it).
1262      */
1263 
1264     if (mode != 2) {
1265 	cptr = search_cache(article, group_hd);
1266 	if (cptr != NULL)
1267 	    goto out;
1268 	cptr = new_cache_slot();
1269     }
1270 
1271     /*
1272      * Copy the article.
1273      */
1274     switch (n = ask_server("%s %ld", mode_cmd[mode], (long) article)) {
1275 	case OK_ARTICLE:
1276 	case OK_HEAD:
1277 	    tmp = open_file(cptr->file_name, OPEN_CREATE | MUST_EXIST);
1278 	    if (copy_text(tmp) < 0)
1279 		return NULL;
1280 
1281 	    if (mode == 1 && !last_copy_blank)
1282 		fputc(NL, tmp);	/* add blank line after header */
1283 
1284 	    if (fclose(tmp) == EOF)
1285 		goto err;
1286 	    cptr->art = article;
1287 	    cptr->grp = group_hd;
1288 	    goto out;
1289 
1290 	case OK_BODY:
1291 	    tmp = open_file(cptr->file_name, OPEN_APPEND | MUST_EXIST);
1292 	    fseek(tmp, 0, 2);
1293 	    if (copy_text(tmp) < 0)
1294 		return NULL;
1295 	    if (fclose(tmp) == EOF)
1296 		goto err;
1297 	    goto out;
1298 
1299 	case ERR_NOARTIG:
1300 	    /* Matt Heffron: ANUNEWS on VMS uses no such article error */
1301 	case ERR_NOART:
1302 	    return NULL;
1303 
1304 	default:
1305 	    /* Matt Heffron: Which group? */
1306 	    log_entry('N', "ARTICLE %ld response: %d (in Group %s)",
1307 		      (long) article, n, group_hd->group_name);
1308 	    nntp_failed = 1;
1309 	    return NULL;
1310     }
1311 
1312 out:
1313     return open_file(cptr->file_name, OPEN_READ | MUST_EXIST);
1314 
1315 err:
1316     /* sys_error('N', "Cannot write temporary file %s", cptr->file_name); */
1317     nn_exitmsg(1, "Cannot write temporary file %s", cptr->file_name);
1318     return NULL;		/* for lint, we can't actually get here */
1319 }
1320 
1321 /*
1322  *	Return local file name holding article
1323  */
1324 
1325 char           *
nntp_get_filename(article_number art,group_header * gh)1326 nntp_get_filename(article_number art, group_header * gh)
1327 {
1328     struct cache   *cptr;
1329 
1330     cptr = search_cache(art, gh);
1331 
1332     return cptr == NULL ? NULL : cptr->file_name;
1333 }
1334 
1335 /*
1336  * nntp_close_server: close the connection to the server.
1337  */
1338 
1339 void
nntp_close_server(void)1340 nntp_close_server(void)
1341 {
1342     if (!is_connected)
1343 	return;
1344 
1345     if (!nntp_failed)		/* avoid infinite recursion */
1346 	put_server("QUIT");
1347 
1348     (void) fclose(nntp_out);
1349     (void) fclose(nntp_in);
1350 
1351     is_connected = 0;
1352 }
1353 
1354 
1355 /*
1356  * nntp_cleanup:  clean up after an nntp session.
1357  *
1358  *	Called from nn_exit().
1359  */
1360 
1361 void
nntp_cleanup(void)1362 nntp_cleanup(void)
1363 {
1364     if (is_connected)
1365 	nntp_close_server();
1366     is_connected = 0;
1367     clean_cache();
1368 }
1369 
1370 
1371 /*************************************************************/
1372 
1373 #ifdef NOV
1374 /*
1375 ** Prime the nntp server to snarf the overview file for a newsgroup.
1376 ** Sends the XOVER command and prepares to read the result.
1377 */
1378 struct novgroup *
nntp_get_overview(group_header * gh,article_number first,article_number last)1379 nntp_get_overview(group_header * gh, article_number first, article_number last)
1380 {
1381     int             n;
1382 
1383     if (!is_connected && connect_server() < 0) {
1384 	return NULL;
1385     }
1386     switch (nntp_set_group(gh)) {
1387 	case -1:
1388 	case 0:
1389 	    return NULL;
1390     }
1391 
1392     n = ask_server("XOVER %d-%d", first, last);
1393     switch (n) {
1394 
1395 	case OK_NOV:
1396 	    return novstream(nntp_in);
1397 
1398 	default:
1399 	    log_entry('N', "XOVER response: %d", n);
1400 	    return NULL;
1401 
1402     }
1403 }
1404 
1405 /*
1406  * nntp_fopen_list(cmd):  Send some variant of a LIST command to the
1407  * NNTP server.  returns NULL if the file to be LISTed doesn't exist
1408  * on the server, else returns the nntp_in FILE descriptor, thus
1409  * simulating fopen().
1410  * nntp_fgets() is later used to read a line from the nntp_in FILE.
1411  */
1412 
1413 FILE           *
nntp_fopen_list(char * cmd)1414 nntp_fopen_list(char *cmd)
1415 {
1416     int             n;
1417 
1418     if (!is_connected && connect_server() < 0)
1419 	return NULL;
1420 
1421     fix_list_response = 1;
1422     switch (n = ask_server(cmd)) {
1423 	case ERR_NOGROUP:	/* really ERR_FAULT - no such file on server */
1424 	    return NULL;
1425 
1426 	case OK_GROUPS:	/* aka NNTP_LIST_FOLLOWS_VAL */
1427 	    return nntp_in;
1428 
1429 	default:
1430 	    log_entry('N', "`%s' response: %d", cmd, n);
1431 	    return NULL;
1432     }
1433 }
1434 
1435 /*
1436  * nntp_fgets() - Get a line from a file stored on the NNTP server.
1437  * Strips any hidden "." at beginning of line and returns a pointer to
1438  * the line.  line will be terminated by NL NUL.
1439  * Returns NULL when NNTP sends the terminating "." to indicate EOF.
1440  */
1441 char           *
nntp_fgets(char * buf,int bufsize)1442 nntp_fgets(char *buf, int bufsize)
1443 {
1444     char           *cp;
1445     register int    size;
1446 
1447     if ((size = get_server_line(buf, bufsize - 1)) < 0)
1448 	return NULL;		/* Can't happen with NOV (we'd rather die
1449 				 * first) */
1450 
1451     cp = buf;
1452     if (*cp == '.') {
1453 	if (*++cp == '\0')
1454 	    return NULL;
1455     }
1456     cp[size] = '\n';
1457     cp[size + 1] = '\0';
1458     return cp;
1459 }
1460 
1461 #endif				/* NOV */
1462 
1463 /* do all the AUTH stuff; used from 3 places that I've found where
1464  * various news servers ask for authentication.  Given that we exit
1465  * on failed auth, probably should just be void.
1466  * OLSON: oops, loops on empty user input "forever". Should fix
1467 */
1468 static void
nntp_doauth(void)1469 nntp_doauth(void)
1470 {
1471     int             len;
1472     char            line[NNTP_STRLEN], *nl, *pass = NULL;
1473 
1474     strcpy(line, "authinfo user ");
1475     len = strlen(line);
1476 
1477     log_entry('N', "NNTP Authentication");
1478 
1479     if (nntp_user) {
1480 	msg("NNTP authentication");
1481 	strcpy(line + len, nntp_user);
1482     } else {
1483 	init_term(1);
1484 	clrdisp();
1485 	prompt("Authentication required; enter username: ");
1486 	(void) fgets(line + len, sizeof(line) - len, stdin);
1487 	if ((nl = strpbrk(line, "\r\n")))
1488 	    *nl = '\0';
1489 	nntp_user = strdup(line + len);
1490     }
1491     put_server(line);
1492     if ((len = get_server(line, sizeof(line))) == OK_NEEDPASS) {
1493 	strcpy(line, "authinfo pass ");
1494 	len = strlen(line);
1495 
1496 	if (nntp_password)
1497 	    strcpy(line + len, nntp_password);
1498 	else {
1499 
1500 #ifdef USEGETPASSPHRASE
1501 	    pass = getpassphrase("Enter password: ");
1502 #else
1503 	    pass = getpass("Enter password: ");
1504 #endif
1505 
1506 	    strcat(line, pass);
1507 	}
1508 	put_server(line);
1509 	if ((len = get_server(line, sizeof(line))) == OK_AUTH) {
1510 	    if (!nntp_password) {
1511 		nntp_password = strdup(pass);
1512 		clrdisp();
1513 		msg("Remembering password for reconnections");
1514 	    }
1515 	} else {
1516 	    nn_exitmsg(1, "Authentication failed (%d): %s", len, line);
1517 	}
1518     }
1519     need_auth = 1;
1520 }
1521 
1522 
1523 /* ZZZZZ */
1524 
1525 /*
1526  * The rest of this is cribbed (and modified) from the old mini-inews.
1527  *
1528  * Simply accept input on stdin (or via a named file) and dump this
1529  * to the server; add a From: and Path: line if missing in the original.
1530  * Print meaningful errors from the server.
1531  * Limit .signature files to MAX_SIGNATURE lines.
1532  *
1533  * Original by Steven Grady <grady@ucbvax.Berkeley.EDU>, with thanks from
1534  * Phil Lapsley <phil@ucbvax.berkeley.edu>
1535  */
1536 
1537 /*
1538  * gen_frompath -- generate From: and Path: lines, in the form
1539  *
1540  *	From: Full Name <user@host.domain>
1541  *	Path: host!user
1542  *
1543  * This routine should only be called if the message doesn't have
1544  * a From: line in it.
1545  */
1546 
1547 static void
gen_frompath(void)1548 gen_frompath(void)
1549 {
1550     struct passwd  *passwd;
1551 
1552     passwd = getpwuid(getuid());
1553 
1554     fprintf(nntp_out, "From: ");
1555     fprintf(nntp_out, "%s ", full_name());
1556 
1557     fprintf(nntp_out, "<%s@%s>\r\n",
1558 	    passwd->pw_name,
1559 	    domain);
1560 
1561 #ifdef HIDDENNET
1562     /* Only the login name - nntp server will add uucp name */
1563     fprintf(nntp_out, "Path: %s\r\n", passwd->pw_name);
1564 #else				/* HIDDENNET */
1565     fprintf(nntp_out, "Path: %s!%s\r\n", host_name, passwd->pw_name);
1566 #endif				/* HIDDENNET */
1567 }
1568 
1569 
1570 /*
1571  * lower -- convert a character to lower case, if it's
1572  *	upper case.
1573  *
1574  *	Parameters:	"c" is the character to be
1575  *			converted.
1576  *
1577  *	Returns:	"c" if the character is not
1578  *			upper case, otherwise the lower
1579  *			case eqivalent of "c".
1580  *
1581  *	Side effects:	None.
1582  */
1583 
1584 static char
lower(register char c)1585 lower(register char c)
1586 {
1587     if (isascii(c) && isupper(c))
1588 	c = c - 'A' + 'a';
1589     return (c);
1590 }
1591 
1592 /*
1593  * strneql -- determine if two strings are equal in the first n
1594  * characters, ignoring case.
1595  *
1596  *	Parameters:	"a" and "b" are the pointers
1597  *			to characters to be compared.
1598  *			"n" is the number of characters to compare.
1599  *
1600  *	Returns:	1 if the strings are equal, 0 otherwise.
1601  *
1602  *	Side effects:	None.
1603  */
1604 
1605 static int
strneql(register char * a,register char * b,int n)1606 strneql(register char *a, register char *b, int n)
1607 {
1608     while (n && lower(*a) == lower(*b)) {
1609 	if (*a == '\0')
1610 	    return (1);
1611 	a++;
1612 	b++;
1613 	n--;
1614     }
1615     if (n)
1616 	return (0);
1617     else
1618 	return (1);
1619 }
1620 
1621 
1622 /*
1623  * valid_header -- determine if a line is a valid header line
1624  *
1625  *	Parameters:	"h" is the header line to be checked.
1626  *
1627  *	Returns:	1 if valid, 0 otherwise
1628  *
1629  *	Side Effects:	none
1630  *
1631  */
1632 
1633 static int
valid_header(register char * h)1634 valid_header(register char *h)
1635 {
1636     char           *colon, *space;
1637 
1638     /*
1639      * blank or tab in first position implies this is a continuation header
1640      */
1641     if (h[0] == ' ' || h[0] == '\t')
1642 	return (1);
1643 
1644     /*
1645      * just check for initial letter, colon, and space to make sure we
1646      * discard only invalid headers
1647      */
1648     colon = index(h, ':');
1649     space = index(h, ' ');
1650     if (isalpha(h[0]) && colon && space == colon + 1)
1651 	return (1);
1652 
1653     /*
1654      * anything else is a bad header -- it should be ignored
1655      */
1656     return (0);
1657 }
1658 
1659 int
nntp_post(char * temp_file)1660 nntp_post(char *temp_file)
1661 {
1662     int             n, response;
1663     FILE           *in = fopen(temp_file, "r");
1664     char            s[4 * NNTP_STRLEN];
1665     int             seen_fromline, in_header, seen_header;
1666     register char  *cp;
1667 
1668     if (!in) {
1669 	sprintf(delayed_msg, "Posting failed because we couldn't re-open file %s.", temp_file);
1670 	return 1;
1671     }
1672     if (!is_connected && connect_server() < 0)
1673 	return 1;
1674 
1675     switch (n = ask_server("POST")) {
1676 	case CONT_POST:
1677 	    break;
1678 	default:
1679 	    sprintf(delayed_msg, "Request to post failed with error %d, %s", n, ask_reply);
1680 	    fclose(in);
1681 	    return 1;
1682     }
1683 
1684     in_header = 1;
1685     seen_header = 0;
1686     seen_fromline = 0;
1687 
1688     while (fgets(s, sizeof(s), in) != NULL) {
1689 	if ((cp = strchr(s, '\n')))
1690 	    *cp = '\0';
1691 	if (s[0] == '.')	/* Single . is eof, so put in extra one */
1692 	    (void) fputc('.', nntp_out);
1693 	if (in_header && strneql(s, "From:", sizeof("From:") - 1)) {
1694 	    seen_header = 1;
1695 	    seen_fromline = 1;
1696 	}
1697 	if (in_header && s[0] == '\0') {
1698 	    if (seen_header) {
1699 		in_header = 0;
1700 		if (!seen_fromline)
1701 		    gen_frompath();
1702 		fprintf(nntp_out, "User-Agent: nn/%s.%s\r\n", RELEASE, PATCHLEVEL);
1703 	    } else {
1704 		continue;
1705 	    }
1706 	} else if (in_header) {
1707 	    if (valid_header(s))
1708 		seen_header = 1;
1709 	    else
1710 		continue;
1711 	}
1712 	fprintf(nntp_out, "%s\r\n", s);
1713     }
1714 
1715     fprintf(nntp_out, ".\r\n");
1716     (void) fflush(nntp_out);
1717     response = get_server(ask_reply, sizeof(ask_reply));
1718     switch (response) {
1719 	case OK_POSTED:
1720 	    break;
1721 	case ERR_POSTFAIL:
1722 	    msg("Article not accepted by server; not posted.");
1723 	    user_delay(2);
1724 	    return 1;
1725 	default:
1726 	    msg("Remote error: %s", ask_reply);
1727 	    user_delay(2);
1728 	    return 1;
1729     }
1730     return 0;
1731 }
1732 
1733 #ifdef NeXT
1734 /*
1735  * posted to comp.sys.next.programmer:
1736  *
1737  *
1738  * From: moser@ifor.math.ethz.ch (Dominik Moser,CLV A4,2 40 19,720 49 89)
1739  * Subject: Re: Compile problems (pgp 2.6.3i)
1740  * Date: 10 Jul 1996 06:50:42 GMT
1741  * Organization: Swiss Federal Institute of Technology (ETHZ)
1742  * References: <4rrhvj$6fr@bagan.srce.hr>
1743  * Message-ID: <4rvjs2$6oh@elna.ethz.ch>
1744  *
1745  * Most systems don't have this (yet)
1746  */
1747 static char    *
strdup(char * str)1748 strdup(char *str)
1749 {
1750     char           *p;
1751 
1752     if ((p = malloc(strlen(str) + 1)) == NULL)
1753 	return ((char *) NULL);
1754 
1755     (void) strcpy(p, str);
1756 
1757     return (p);
1758 }
1759 
1760 #endif				/* NeXT */
1761 
1762 #endif				/* NNTP */
1763