home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Unix System Administration Handbook 1997 October
/
usah_oct97.iso
/
news
/
nn.tar
/
nn-6.5.1
/
nntp.c
< prev
next >
Wrap
C/C++ Source or Header
|
1996-08-26
|
31KB
|
1,290 lines
/*
* nntp module for nn.
*
* The original taken from the nntp 1.5 clientlib.c
* Modified heavily for nn.
*
* Rene' Seindal (seindal@diku.dk) Thu Dec 1 18:41:23 1988
*
* I have modified Rene's code quite a lot for 6.4 -- I hope he
* can still recognize a bit here and a byte there; in any case,
* any mistakes are mine :-) ++Kim
*/
#include "config.h"
/*
* nn maintains a cache of recently used articles to improve efficiency.
* To change the size of the cache, define NNTPCACHE in config.h to be
* the new size of this cache.
*/
#ifndef NNTPCACHE
#define NNTPCACHE 10
#endif
#ifdef NNTP
#include <stdio.h>
#include "nntp.h"
#include <sys/socket.h>
#ifndef EXCELAN
#include <netdb.h>
#endif
#include <errno.h>
#ifdef NOV
#include "hash.h"
#include "hdbm.h"
#include "newsoverview.h"
#endif
/* This is necessary due to the definitions in m-XXX.h */
#if !defined(NETWORK_DATABASE) || defined(NETWORK_BYTE_ORDER)
#include <netinet/in.h>
#endif
#ifdef EXCELAN
#ifndef IPPORT_NNTP
#define IPPORT_NNTP 119
#endif
#endif
/* nntp.c */
static debug_msg __APROTO((char *prefix, char *str));
static io_error __APROTO((void));
static void find_server __APROTO((void));
static get_server_line __APROTO((char *string, int size));
static get_server __APROTO((char *string, int size));
static get_socket __APROTO((void));
static connect_server __APROTO((void));
static put_server __APROTO((char *string));
static ask_server __APROTO(());
static copy_text __APROTO((register FILE *fp));
static do_set_group __APROTO((void));
static struct cache *search_cache __APROTO((article_number art, group_header *gh));
static struct cache *new_cache_slot __APROTO((void));
static void clean_cache __APROTO((void));
static sort_art_list __APROTO((register article_number *f1, register article_number *f2));
import char *db_directory, *tmp_directory, *news_active;
export char nntp_server[256]; /* name of nntp server */
export int nntp_failed = 0; /* bool: t iff connection is broken in
nntp_get_article() or nntp_get_active() */
export int nntp_cache_size = NNTPCACHE;
export char *nntp_cache_dir = NULL;
export int nntp_local_server = 0;
export int nntp_debug = 0;
import int silent, no_update;
import int sys_nerr;
#ifndef __NetBSD__
import char *sys_errlist[];
#endif /* __NetBSD__ */
extern void nn_exitmsg();
extern void sys_error();
extern int sys_warning();
#define syserr() (errno >= 0 && errno < sys_nerr ? \
sys_errlist[errno] : "Unknown error.")
import char *mktemp();
static FILE *nntp_in = NULL; /* fp for reading from server */
static FILE *nntp_out = NULL; /* fp for writing to server */
static int is_connected = 0; /* bool: t iff we are connected */
static group_header *group_hd; /* ptr to servers current group */
static int group_is_set = 0; /* bool: t iff group_hd is set */
static int try_again = 0; /* bool: t if timeout forces retry */
static int can_post = 0; /* bool: t iff NNTP server accepts postings */
#define ERR_TIMEOUT 503 /* Response code for timeout */
/* Same value as ERR_FAULT */
#ifdef NO_RENAME
static rename(old, new)
char *old, *new;
{
if (unlink(new) < 0 && errno != ENOENT) return -1;
if (link(old, new) < 0) return -1;
return unlink(old);
}
#endif /* NO_RENAME */
/*
* debug_msg: print a debug message.
*
* The master appends prefix and str to a log file, and clients
* prints it as a message.
*
* This is controlled via the nntp-debug variable in nn, and
* the option -D2 (or -D3 if the normal -D option should also
* be turned on). Debug output from the master is written in
* $TMP/nnmaster.log.
*/
static debug_msg(prefix, str)
char *prefix, *str;
{
static FILE *f = NULL;
if (who_am_i == I_AM_MASTER) {
if (f == NULL) {
f = open_file(relative(tmp_directory, "nnmaster.log"), OPEN_CREATE);
if (f == NULL) {
nntp_debug = 0;
return;
}
}
fprintf(f, "%s %s\n", prefix, str);
fflush(f);
return;
}
msg("NNTP%s %s", prefix, str);
user_delay(1);
}
/*
* io_error: signal an I/O error in talking to the server.
*
* An nn client terminates a session with the user. The master
* simply closes the connection. The flag nntp_failed is set, for
* use by the master to terminate collection.
*
* BUG: if the nntp server is forcibly killed, errno can contain a
* bogus value, resulting in strange error messages. It is
* probably better just to write out the numerical value of errno.
*/
static io_error()
{
if (who_am_i != I_AM_MASTER) {
nn_exitmsg(1, "Lost connection to NNTP server %s: %s", nntp_server, syserr());
/* NOTREACHED */
}
nntp_failed = 1;
if (is_connected) {
log_entry('N', "Lost connection to server %s: %s", nntp_server, syserr());
nntp_close_server();
}
}
/*
* find_server: Find out which host to use as NNTP server.
*
* This is done by consulting the file NNTP_SERVER (defined in
* config.h). Set nntp_server[] to the host's name.
*/
static void find_server()
{
char *cp, *name, *getenv();
char buf[BUFSIZ];
FILE *fp;
/*
* This feature cannot normally be enabled, because the database and
* the users rc file contains references to articles by number, and
* these numbers are not unique across NNTP servers.
*/
#ifdef NOV
/*
* But, since we no longer have a database to worry about, give the user
* the rope. If he wants to hang himself, then let him! :-)
* Let the user worry about keeping his .newsrc straight.
*/
if ((cp = getenv("NNTPSERVER")) != NULL) {
strncpy(nntp_server, cp, sizeof nntp_server);
return;
}
#endif /* NOV */
name = NNTP_SERVER;
if (*name != '/')
name = relative(lib_directory, name);
if ((fp = open_file(name, OPEN_READ)) != NULL) {
while (fgets(buf, sizeof buf, fp) != 0) {
if (*buf == '#' || *buf == '\n')
continue;
if ((cp = strchr(buf, '\n')) != 0)
*cp = '\0';
strncpy(nntp_server, buf, sizeof nntp_server);
fclose(fp);
return;
}
fclose(fp);
}
if (who_am_i != I_AM_MASTER)
printf("\nCannot find name of NNTP server.\nCheck %s\n", name);
sys_error("Failed to find name of NNTP server!");
}
/*
* get_server_line: get a line from the server.
*
* Expects to be connected to the server.
* The line can be any kind of line, i.e., either response or text.
* Returns length of line if no error.
* If error and master, then return -1, else terminate.
*/
static int
get_server_line(string, size)
register char *string;
register int size;
{
if (fgets(string, size, nntp_in) == NULL) {
io_error();
return -1;
}
size = strlen(string);
if (size < 2 || !(string[size-2] == '\r' && string[size-1] == '\n'))
return size; /* XXX */
string[size-2] = '\0'; /* nuke CRLF */
return size-2;
}
/*
* get_server: get a response line from the server.
*
* Expects to be connected to the server.
* Returns the numerical value of the reponse, or -1 in case of errors.
*/
static get_server(string, size)
char *string;
int size;
{
if (get_server_line(string, size) < 0)
return -1;
if (nntp_debug) debug_msg("<<<", string);
return isdigit(*string) ? atoi(string) : 0;
}
/*
* get_socket: get a connection to the nntp server.
*
* Errors can happen when YP services or DNS are temporarily down or
* hung, so we log errors and return failure rather than exitting if we
* are the master. The effects of retrying every 15 minutes (or whatever
* the -r interval is) are not that bad. Dave Olson, SGI
*/
static get_socket()
{
int s;
struct sockaddr_in sin;
#ifndef EXCELAN
struct servent *getservbyname(), *sp;
struct hostent *gethostbyname(), *hp;
#ifdef h_addr
int x = 0;
register char **cp;
#endif /* h_addr */
if ((sp = getservbyname("nntp", "tcp")) == NULL)
return sys_warning("nntp/tcp: Unknown service.\n");
s = who_am_i == I_AM_MASTER ? 10 : 2;
while ((hp = gethostbyname(nntp_server)) == NULL) {
if (--s < 0) goto host_err;
sleep(10);
}
clearobj(&sin, sizeof(sin), 1);
sin.sin_family = hp->h_addrtype;
sin.sin_port = sp->s_port;
#else /* EXCELAN */
char *machine;
clearobj(&sin, sizeof(sin), 1);
sin.sin_family = AF_INET;
sin.sin_port = htons(0);
#endif /* EXCELAN */
#ifdef h_addr
/* get a socket and initiate connection -- use multiple addresses */
s = x = -1;
for (cp = hp->h_addr_list; cp && *cp; cp++) {
s = socket(hp->h_addrtype, SOCK_STREAM, 0);
if (s < 0) goto sock_err;
#ifdef NO_MEMMOVE
bcopy(*cp, (char *)&sin.sin_addr, hp->h_length);
#else
memmove((char *)&sin.sin_addr, *cp, hp->h_length);
#endif /* NO_MEMMOVE */
/* Quick hack to work around interrupting system calls.. */
while((x = connect(s, (struct sockaddr *)&sin, sizeof (sin))) < 0 &&
errno == EINTR) sleep(1);
if (x == 0)
break;
if (who_am_i != I_AM_MASTER)
msg("Connecting to %s failed: %s", nntp_server, syserr());
(void) close(s);
s = -1;
}
if (x < 0)
sys_warning("Giving up on NNTP server %s!", nntp_server);
#else /* h_addr */ /* no name server */
#ifdef EXCELAN
if ((s = socket(SOCK_STREAM, NULL, &sin, SO_KEEPALIVE)) < 0)
goto sock_err;
sin.sin_port = htons(IPPORT_NNTP);
machine = nntp_server;
if ((sin.sin_addr.s_addr = rhost(&machine)) == -1) {
(void) close(s);
goto host_err;
}
/* And then connect */
if (connect(s, &sin) < 0)
goto conn_err;
#else /* not EXCELAN */
if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)
goto sock_err;
/* And then connect */
#ifdef NO_MEMMOVE
bcopy(hp->h_addr, (char *) &sin.sin_addr, hp->h_length);
#else
memmove((char *)&sin.sin_addr, hp->h_addr, hp->h_length);
#endif /* NO_MEMMOVE */
if (connect(s, (struct sockaddr *) &sin, sizeof(sin)) < 0)
goto conn_err;
#endif /* EXCELAN */
#endif /* h_addr */
return s;
host_err:
sys_warning("NNTP server %s unknown.\n", nntp_server);
return -1;
sock_err:
sys_warning("Can't get NNTP socket: %s", syserr());
return -1;
conn_err:
(void) close(s);
if (who_am_i == I_AM_MASTER)
sys_warning("Connecting to %s failed: %s", nntp_server, syserr());
return -1;
}
/*
* connect_server: initialise a connection to the nntp server.
*
* It expects nntp_server[] to be set previously, by a call to
* nntp_check. It is called from nntp_get_article() and
* nntp_get_active() if there is no established connection.
*/
static connect_server()
{
int sockt_rd, sockt_wr;
int response, response2;
char line[NNTP_STRLEN];
if (who_am_i != I_AM_MASTER && !silent)
msg("Connecting to NNTP server %s ...", nntp_server);
nntp_failed = 1;
is_connected = 0;
sockt_rd = get_socket();
if (sockt_rd < 0)
return -1;
if ((nntp_in = fdopen(sockt_rd, "r")) == NULL) {
close(sockt_rd);
return -1;
}
setvbuf(nntp_in, NULL, _IOLBF, 0);
sockt_wr = dup(sockt_rd);
if ((nntp_out = fdopen(sockt_wr, "w")) == NULL) {
close(sockt_wr);
fclose(nntp_in);
nntp_in = NULL; /* from above */
return -1;
}
/* Now get the server's signon message */
response = get_server(line, sizeof(line));
if (who_am_i == I_AM_MASTER) {
if (response != OK_CANPOST && response != OK_NOPOST) {
log_entry('N', "Failed to connect to NNTP server");
log_entry('N', "Response: %s", line);
fclose(nntp_out);
fclose(nntp_in);
return -1;
}
} else {
switch (response) {
case OK_CANPOST:
can_post = 1;
break;
case OK_NOPOST:
can_post = 0;
break;
default:
nn_exitmsg(1, line);
/* NOTREACHED */
}
}
/* Try and speak NNRP style reader protocol */
sprintf(line, "MODE READER");
if (put_server(line) >= 0) {
/* See what we got if from a NNRP compiant server like INN's nnrpd */
response = get_server(line, sizeof(line));
switch (response) {
case OK_CANPOST:
can_post = 1;
break;
case OK_NOPOST:
can_post = 0;
break;
case ERR_COMMAND:
default:
/* if it doesn't understand MODE READER, we dont care.. :-) */
break;
}
}
if (who_am_i != I_AM_MASTER && !silent)
msg("Connecting to NNTP server %s ... ok (%s)",
nntp_server, can_post ? "posting is allowed" : "no posting");
is_connected = 1;
group_is_set = 0;
nntp_failed = 0;
try_again = 0;
return 0;
}
/*
* put_server: send a line to the nntp server.
*
* Expects to be connected to the server.
*/
static put_server(string)
char *string;
{
if (nntp_debug) debug_msg(">>>", string);
fprintf(nntp_out, "%s\r\n", string);
if (fflush(nntp_out) == EOF) {
io_error();
return -1;
}
return 0;
}
/*
* ask_server: ask the server a question and return the answer.
*
* Expects to be connected to the server.
* Returns the numerical value of the reponse, or -1 in case of
* errors.
* Contains some code to handle server timeouts intelligently.
*/
/* LIST XXX returns fatal ERR_FAULT code if requested list does not exist */
/* This is only fatal for LIST ACTIVE -- else change to ERR_NOGROUPS */
static int fix_list_response = 0;
/*VARARGS*/
static ask_server(va_alist)
va_dcl
{
char buf[NNTP_STRLEN];
char *fmt;
int response;
int fix_err;
use_vararg;
fix_err = fix_list_response;
fix_list_response = 0;
start_vararg;
fmt = va_arg1(char *);
vsprintf(buf, fmt, va_args2toN);
end_vararg;
if (put_server(buf) < 0)
return -1;
response = get_server(buf, sizeof(buf));
/*
* Handle the response from the server. Responses are handled as
* followes:
*
* 100-199 Informational. Passed back. (should they be ignored?).
* 200-299 Ok messages. Passed back.
* 300-399 Ok and proceed. Can not happen in nn.
* 400-499 Errors (no article, etc). Passed up and handled there.
* 500-599 Fatal NNTP errors. Handled below.
*/
if (response == ERR_GOODBYE || response > ERR_COMMAND) {
if (fix_err && response == ERR_FAULT) return ERR_NOGROUP;
nntp_failed = 1;
nntp_close_server();
if (response != ERR_TIMEOUT && response != ERR_GOODBYE) {
/* if not timeout and not goodbye, complain */
sys_error("NNTP %s response: %d", buf, response);
/* NOTREACHED */
}
if (response == ERR_GOODBYE) {
sys_warning("NNTP %s response: %d", buf, response);
}
try_again = 1;
group_is_set = 0;
}
return response;
}
/*
* copy_text: copy text response into file.
*
* Copies a text response into an open file.
* Return -1 on error, 0 otherwise. It is treated as an error, if
* the returned response it not what was expected.
*/
static int last_copy_blank;
static copy_text(fp)
register FILE *fp;
{
char buf[NNTP_STRLEN];
register char *cp;
register int nlines;
nlines = 0;
last_copy_blank = 0;
while (get_server_line(buf, sizeof buf) >= 0) {
cp = buf;
if (*cp == '.')
if (*++cp == '\0') {
if (nlines <= 0) break;
if (nntp_debug) {
sprintf(buf, "%d lines", nlines);
debug_msg("COPY", buf);
}
return 0;
}
last_copy_blank = (*cp == NUL);
fputs(cp, fp);
putc('\n', fp);
nlines++;
}
fclose(fp);
if (nntp_debug) debug_msg("COPY", "EMPTY");
return -1;
}
static do_set_group()
{
int n;
switch (n = ask_server("GROUP %s", group_hd->group_name)) {
case OK_GROUP:
group_is_set = 1;
return 1;
case ERR_NOGROUP:
log_entry('N', "NNTP: group %s not found", group_hd->group_name);
return -1;
default:
if (try_again) return 0; /* Handle nntp server timeouts */
break;
}
if (!nntp_failed) {
log_entry('N', "GROUP %s response: %d", group_hd->group_name, n);
nntp_failed = 1;
}
return -1;
}
/*
* The following functions implements a simple lru cache of recently
* accessed articles. It is a simple way to improve effeciency. Files
* must be kept by name, because the rest of the code expects to be able
* to open an article multiple times, and get separate file pointers.
*/
struct cache {
char *file_name; /* file name */
article_number art; /* article stored in file */
group_header *grp; /* from this group */
unsigned time; /* time last accessed */
} cache[NNTPCACHE];
static unsigned time_counter = 1; /* virtual time */
/*
* search_cache: search the cache for an (article, group) pair.
*
* Returns a pointer to the slot where it is, null otherwise
*/
static struct cache *search_cache(art, gh)
article_number art;
group_header *gh;
{
struct cache *cptr = cache;
int i;
if (who_am_i == I_AM_MASTER) return NULL;
if (nntp_cache_size > NNTPCACHE) nntp_cache_size = NNTPCACHE;
for (i = 0; i < nntp_cache_size; i++, cptr++)
if (cptr->art == art && cptr->grp == gh) {
cptr->time = time_counter++;
return cptr;
}
return NULL;
}
/*
* new_cache_slot: get a free cache slot.
*
* Returns a pointer to the allocated slot.
* Frees the old filename, and allocates a new, unused filename.
* Cache files can also stored in a common directory defined in
* ~/.nn or CACHE_DIRECTORY if defined in config.h.
*/
static struct cache *new_cache_slot()
{
register struct cache *cptr = cache;
int i, lru;
unsigned min_time = time_counter;
char name[FILENAME];
if (nntp_cache_dir == NULL) {
#ifdef CACHE_DIRECTORY
nntp_cache_dir = CACHE_DIRECTORY;
#else /* CACHE_DIRECTORY */
if (who_am_i == I_AM_MASTER)
nntp_cache_dir = db_directory;
else
nntp_cache_dir = nn_directory;
#endif /* CACHE_DIRECTORY */
}
if (who_am_i == I_AM_MASTER) {
cptr = &cache[0];
if (cptr->file_name == NULL)
cptr->file_name = mk_file_name(nntp_cache_dir, "master_cache");
return cptr;
}
for (i = 0; i < nntp_cache_size; i++, cptr++)
if (min_time > cptr->time) {
min_time = cptr->time;
lru = i;
}
cptr = &cache[lru];
if (cptr->file_name == NULL) {
sprintf(name, "%s/nn-%d.%02d~", nntp_cache_dir, process_id, lru);
cptr->file_name = copy_str(name);
} else
unlink(cptr->file_name);
cptr->time = time_counter++;
return cptr;
}
/*
* clean_cache: clean up the cache.
*
* Removes all allocated files.
*/
static void clean_cache()
{
struct cache *cptr = cache;
int i;
for (i = 0; i < nntp_cache_size; i++, cptr++)
if (cptr->file_name)
unlink(cptr->file_name);
}
/*
* nntp_check: Find out whether we need to use NNTP.
*
* This is done by comparing the NNTP servers name with whatever
* nn_gethostname() returns.
* use_nntp and news_active are initialised as a side effect.
*/
nntp_check()
{
char host[128];
char *server_real_name;
if (nntp_local_server) return;
find_server();
nn_gethostname(host, sizeof host);
strncpy(host, (gethostbyname(host))->h_name, sizeof host);
server_real_name = (gethostbyname(nntp_server))->h_name;
use_nntp = (strcmp(host, server_real_name) != 0);
if (use_nntp) {
freeobj(news_active);
#ifndef NOV
news_active = mk_file_name(db_directory, "ACTIVE");
#else /* NOV */
news_active = mk_file_name(nn_directory, "ACTIVE");
#endif /* NOV */
}
}
/*
* nntp_no_post: Check to see whether posting is allowed.
*/
nntp_no_post()
{
if (!is_connected && connect_server() < 0)
return 1; /* If we cannot connect, neither can inews */
if (can_post == 0) {
msg("NNTP server does not allow postings from this host. Sorry!");
return 1;
}
return 0;
}
/*
* nntp_set_group: set the server's current group.
*
* Actual communication is delayed until an article is accessed, to
* avoid unnecessary traffic.
*/
nntp_set_group(gh)
group_header *gh;
{
group_hd = gh;
group_is_set = 0;
return 0;
}
#ifndef NOV
/*
* nntp_get_active: get a copy of the active file.
*
* If we are the master get a copy of the file from the nntp server.
* nnadmin just uses the one we already got. In this way the master
* can maintain a remote copy of the servers active file.
* We try to be a little smart, if not inefficient, about the
* modification times on the local active file.
* Even when the master is running on the nntp server, a separate
* copy of the active file will be made for access via NFS.
*/
nntp_get_active()
{
FILE *old, *new;
char bufo[NNTP_STRLEN], bufn[NNTP_STRLEN];
char *new_name;
int same, n;
if (who_am_i != I_AM_MASTER)
return access(news_active, 4);
again:
if (!is_connected && connect_server() < 0)
return -1;
new_name = mktemp(relative(db_directory, ".actXXXXXX"));
switch (n = ask_server("LIST")) {
case OK_GROUPS:
new = open_file(new_name, OPEN_CREATE_RW|MUST_EXIST);
if (copy_text(new) == 0) {
if (fflush(new) != EOF) break;
fclose(new);
}
unlink(new_name);
if (!nntp_failed) {
log_entry('N', "LIST empty");
nntp_failed = 1;
}
return -1;
default:
if (try_again) goto again; /* Handle nntp server timeouts */
log_entry('N', "LIST response: %d", n);
return -1;
}
rewind(new);
same = 0;
if ((old = open_file(news_active, OPEN_READ)) != NULL) {
do {
fgets(bufo, sizeof bufo, old);
fgets(bufn, sizeof bufn, new);
} while (!feof(old) && !feof(new) && strcmp(bufo, bufn) == 0);
same = feof(old) && feof(new);
fclose(old);
}
fclose(new);
if (same)
unlink(new_name);
else
if (rename(new_name, news_active) != 0)
sys_error("Cannot rename %s to %s", new_name, news_active);
return 0;
}
#endif /* NOV */
/*
* nntp_get_newsgroups: get a copy of the newsgroups file.
*
* Use the "LIST NEWSGROUPS" command to get the newsgroup descriptions.
* Based on code from: olson%anchor.esd@sgi.com (Dave Olson)
*/
FILE *nntp_get_newsgroups()
{
char *new_name;
FILE *new;
int n;
new_name = mktemp(relative(tmp_directory, "nngrXXXXXX"));
new = open_file(new_name, OPEN_CREATE_RW|OPEN_UNLINK);
if (new == NULL) return NULL;
again:
if (!is_connected && connect_server() < 0) goto err;
fix_list_response = 1;
switch (n = ask_server("LIST NEWSGROUPS")) {
case ERR_NOGROUP: /* really ERR_FAULT */
goto err;
case OK_GROUPS:
if (copy_text(new) == 0) {
if (fflush(new) != EOF) break;
fclose(new);
}
if (!nntp_failed) {
log_entry('N', "LIST NEWSGROUPS empty");
nntp_failed = 1;
}
return NULL;
default:
if (try_again) goto again; /* Handle nntp server timeouts */
log_entry('N', "LIST NEWSGROUPS response: %d", n);
goto err;
}
rewind(new);
return new;
err:
fclose(new);
return NULL;
}
#ifndef NOV
/*
* nntp_get_article_list: get list of all article numbers in group
*
* Sends XHDR command to the server, and parses the following
* text response to get a list of article numbers which is saved
* in a list and returned.
* Return NULL on error. It is treated as an error, if
* the returned response it not what was expected.
*/
static article_number *article_list = NULL;
static long art_list_length = 0;
static sort_art_list(f1, f2)
register article_number *f1, *f2;
{
return (*f1 < *f2) ? -1 : (*f1 == *f2) ? 0 : 1;
}
article_number *nntp_get_article_list(gh)
group_header *gh;
{
char buf[NNTP_STRLEN];
register article_number *art;
register char *cp;
register long count = 0; /* No. of completions plus one */
int n;
static int try_listgroup = 1;
again:
if (!is_connected && connect_server() < 0)
return NULL;
/* it is really an extreme waste of time to use XHDR since all we */
/* are interested in is the article numbers (as we do locally). */
/* If somebody hacks up an nntp server that understands LISTGROUP */
/* they will get much less load on the nntp server */
/* It should simply return the existing article numbers is the group*/
/* -- they don't even have to be sorted (only XHDR needs that) */
if (try_listgroup) {
switch (n = ask_server("LISTGROUP %s", group_hd->group_name)) {
case OK_GROUP:
break;
default:
if (try_again) goto again; /* Handle nntp server timeouts */
log_entry('N', "LISTGROUP response: %d", n);
return NULL;
case ERR_COMMAND:
try_listgroup = 0;
goto again; /* error may have closed down server connection */
}
}
if (!try_listgroup) {
if (group_is_set == 0)
switch (do_set_group()) {
case -1:
return NULL;
case 0:
goto again;
case 1:
break;
}
switch (n = ask_server("XHDR message-id %ld-%ld",
(long)gh->first_db_article, (long)gh->last_db_article)) {
case OK_HEAD:
break;
default:
if (try_again) goto again; /* Handle nntp server timeouts */
log_entry('N', "XHDR response: %d", n);
return NULL;
case ERR_COMMAND:
nntp_failed = 2;
return NULL;
}
}
count = 0;
art = article_list;
while (get_server_line(buf, sizeof buf) >= 0) {
cp = buf;
if (*cp == '.' && *++cp == '\0') break;
if (count == art_list_length) {
art_list_length += 250;
article_list = resizeobj(article_list, article_number, art_list_length + 1);
art = article_list + count;
}
*art++ = atol(cp);
count++;
}
if (article_list != NULL) {
*art = 0;
if (try_listgroup && count > 1)
quicksort(article_list, count, article_number, sort_art_list);
}
return article_list;
}
#endif /* NOV */
/*
* nntp_get_article: get an article from the server.
*
* Returns a FILE pointer.
* If necessary the server's current group is set.
* The article (header and body) are copied into a file, so they
* are seekable (nn likes that).
*/
static char *mode_cmd[] = {
"ARTICLE",
"HEAD",
"BODY"
};
FILE *nntp_get_article(article, mode)
article_number article;
int mode; /* 0 => whole article, 1 => head only, 2 => body only */
{
FILE *tmp;
static struct cache *cptr;
int n;
again:
if (!is_connected && connect_server() < 0) {
return NULL;
}
/*
* Set the server group to the current group
*/
if (group_is_set == 0)
switch (do_set_group()) {
case -1:
return NULL;
case 0:
goto again;
case 1:
break;
}
/*
* Search the cache for the requested article, and allocate a new
* slot if necessary (if appending body, we already got it).
*/
if (mode != 2) {
cptr = search_cache(article, group_hd);
if (cptr != NULL) goto out;
cptr = new_cache_slot();
}
/*
* Copy the article.
*/
switch (n = ask_server("%s %ld", mode_cmd[mode], (long)article)) {
case OK_ARTICLE:
case OK_HEAD:
tmp = open_file(cptr->file_name, OPEN_CREATE|MUST_EXIST);
if (copy_text(tmp) < 0)
return NULL;
if (mode == 1 && !last_copy_blank)
fputc(NL, tmp); /* add blank line after header */
if (fclose(tmp) == EOF) goto err;
cptr->art = article;
cptr->grp = group_hd;
goto out;
case OK_BODY:
tmp = open_file(cptr->file_name, OPEN_APPEND|MUST_EXIST);
fseek(tmp, (off_t)0, 2);
if (copy_text(tmp) < 0)
return NULL;
if (fclose(tmp) == EOF) goto err;
goto out;
case ERR_NOARTIG:
/* Matt Heffron: ANUNEWS on VMS uses no such article error */
case ERR_NOART:
return NULL;
default:
if (try_again) goto again; /* Handle nntp server timeouts */
/* Matt Heffron: Which group? */
log_entry('N', "ARTICLE %ld response: %d (in Group %s)",
(long)article, n, group_hd->group_name);
nntp_failed = 1;
return NULL;
}
out:
return open_file(cptr->file_name, OPEN_READ|MUST_EXIST);
err:
sys_error('N', "Cannot write temporary file %s", cptr->file_name);
}
/*
* Return local file name holding article
*/
char *nntp_get_filename(art, gh)
article_number art;
group_header *gh;
{
struct cache *cptr;
cptr = search_cache(art, gh);
return cptr == NULL ? NULL : cptr->file_name;
}
/*
* nntp_close_server: close the connection to the server.
*/
void
nntp_close_server()
{
if (!is_connected)
return;
if (!nntp_failed) { /* avoid infinite recursion */
int n;
n = ask_server("QUIT");
if (n != OK_GOODBYE)
; /* WHAT NOW ??? */
}
(void) fclose(nntp_out);
(void) fclose(nntp_in);
is_connected = 0;
}
/*
* nntp_cleanup: clean up after an nntp session.
*
* Called from nn_exit().
*/
nntp_cleanup()
{
if (is_connected)
nntp_close_server();
clean_cache();
}
/*************************************************************/
#ifdef NOV
/*
** Prime the nntp server to snarf the overview file for a newsgroup.
** Sends the XOVER command and prepares to read the result.
*/
struct novgroup *
nntp_get_overview(gh, first, last)
group_header *gh;
article_number first, last;
{
int n;
again:
if (!is_connected && connect_server() < 0) {
return NULL;
}
nntp_set_group(gh);
switch (do_set_group()) {
case -1:
return NULL;
case 0:
goto again;
}
n = ask_server("XOVER %d-%d", first, last);
switch (n) {
case OK_NOV:
return novstream(nntp_in);
default:
if (try_again)
goto again; /* Handle nntp server timeouts */
log_entry('N', "XOVER response: %d", n);
return NULL;
}
}
/*
* nntp_fopen_list(cmd): Send some variant of a LIST command to the
* NNTP server. returns NULL if the file to be LISTed doesn't exist
* on the server, else returns the nntp_in FILE descriptor, thus
* simulating fopen().
* nntp_fgets() is later used to read a line from the nntp_in FILE.
*/
FILE *
nntp_fopen_list(cmd)
char *cmd;
{
int n;
again:
if (!is_connected && connect_server() < 0)
return NULL;
fix_list_response = 1;
switch (n = ask_server(cmd)) {
case ERR_NOGROUP: /* really ERR_FAULT - no such file on server */
return NULL;
case OK_GROUPS: /* aka NNTP_LIST_FOLLOWS_VAL */
return nntp_in;
default:
if (try_again) goto again; /* Handle nntp server timeouts */
log_entry('N', "`%s' response: %d", cmd, n);
return NULL;
}
}
/*
* nntp_fgets() - Get a line from a file stored on the NNTP server.
* Strips any hidden "." at beginning of line and returns a pointer to
* the line. line will be terminated by NL NUL.
* Returns NULL when NNTP sends the terminating "." to indicate EOF.
*/
char *
nntp_fgets(buf, bufsize)
char *buf;
int bufsize;
{
char *cp;
register int size;
if ((size = get_server_line(buf, bufsize - 1)) < 0)
return NULL; /* Can't happen with NOV (we'd rather die first) */
cp = buf;
if (*cp == '.') {
if (*++cp == '\0')
return NULL;
}
cp[size] = '\n';
cp[size+1] = '\0';
return cp;
}
#endif /* NOV */
#endif /* NNTP */