/* * Copyright (c) 1996-2005 Michael T Pins. All rights reserved. * * 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 #include #include #include #include #include #include "config.h" #include "global.h" #include "fullname.h" #include "hash.h" #include "hostname.h" #include "libnov.h" #include "nn_term.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 "nntp.h" #include "patchlevel.h" #include #include #include #include #ifndef EXCELAN #include #endif #include #include #ifdef NOV #include "newsoverview.h" #endif /* This is necessary due to the definitions in m-XXX.h */ #if !defined(NETWORK_DATABASE) || defined(NETWORK_BYTE_ORDER) #include #endif #ifdef EXCELAN #ifndef IPPORT_NNTP #define IPPORT_NNTP 119 #endif #endif /* nntp.c */ static int reconnect_server(int); static int connect_server(void); static void debug_msg(char *prefix, char *str); static void find_server(void); static char *find_domain(const char *domainFile); static int get_server_line(char *string, int size); static int get_server(char *string, int size); static int get_socket(void); static void put_server(char *string); static int copy_text(register FILE * fp); static struct cache *search_cache(article_number art, group_header * gh); static struct cache *new_cache_slot(void); static void clean_cache(void); static void set_domain(void); static void nntp_doauth(void); #ifndef NOV static int sort_art_list(register article_number * f1, register article_number * f2); #endif /* !NOV */ #ifdef NeXT static char *strdup(char *); #endif /* NeXT */ extern char *db_directory, *tmp_directory, *news_active; extern char delayed_msg[]; static char host_name[MAXHOSTNAMELEN]; char domain[MAXHOSTNAMELEN]; static char last_put[NNTP_STRLEN]; int nntp_failed = 0;/* bool: t iff connection is broken in * nntp_get_article() or nntp_get_active() */ int nntp_cache_size = NNTPCACHE; char *nntp_cache_dir = NULL; char *nntp_server = NNTP_SERVER; /* name of nntp server */ char *nntp_user, *nntp_password; /* so can set on command line */ int nntp_local_server = 0; int nntp_debug = 0; extern char *home_directory; extern int silent; extern char *mktemp(); static FILE *nntp_in = NULL; /* fp for reading from server */ static FILE *nntp_out = NULL;/* fp for writing to server */ static int reconnecting = 0; static int is_connected = 0; /* bool: t iff we are connected */ static int need_auth = 0; static group_header *group_hd; /* ptr to servers current group */ 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 int rename(char *old, char *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 void debug_msg(char *prefix, char *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(3); } /* * 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(void) { char *cp, *name; 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) { nntp_server = cp; return; } else if (*nntp_server != '/') return; /* variable was set on cmd line, or in init * file */ #endif /* NOV */ name = nntp_server; /* default, or variable was set to a filename */ 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'; nntp_server = strdup(buf); if (!nntp_server) sys_error("Failed to allocate space for name of 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!"); } /* * find_domain Get the domain name for posting from a named file. * Handle blank lines and comments. * * Parameters: "file" is the name of the file to read. * * Returns: Pointer to static data area containing the * first non-blank/comment line in the file. * NULL on error (or lack of entry in file). * * Side effects: None. */ static char * find_domain(const char *domainFile) { register FILE *fp; register char *cp; static char buf[MAXHOSTNAMELEN]; char *index(); if (domainFile == NULL) return (NULL); fp = fopen(domainFile, "r"); if (fp == NULL) return (NULL); while (fgets(buf, sizeof (buf), fp) != NULL) { if (*buf == '\n' || *buf == '#') continue; cp = index(buf, '\n'); if (cp) *cp = '\0'; (void) fclose(fp); return (buf); } (void) fclose(fp); return (NULL); } /* * 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(register char *string, register int size) { int retry = 2; while (fgets(string, size, nntp_in) == NULL) retry = (reconnect_server(retry)); 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 int get_server(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 int get_socket(void) { int s; struct sockaddr_in sin; #ifndef EXCELAN struct servent *sp; struct hostent *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, strerror(errno)); (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", strerror(errno)); return -1; #ifndef h_addr conn_err: (void) close(s); if (who_am_i == I_AM_MASTER) sys_warning("Connecting to %s failed: %s", nntp_server, strerror(errno)); return -1; #endif } /* * 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. * This gets called at initialization, and whenever it looks * like the server may have closed the connection on us. */ static int connect_server(void) { int sockt_rd, sockt_wr; int response; int triedauth = 0; char line[NNTP_STRLEN]; if (who_am_i == I_AM_VIEW) nntp_check(); if (who_am_i != I_AM_MASTER && !silent && !reconnecting) 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; } 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 if (reconnecting && need_auth) { nntp_doauth(); triedauth++; } else { switch (response) { case OK_AUTH: break; case OK_CANPOST: can_post = 1; break; case OK_NOPOST: can_post = 0; break; default: nn_exitmsg(1, "%s", line); /* NOTREACHED */ } } retrymode: /* Try and speak NNRP style reader protocol */ sprintf(line, "MODE READER"); put_server(line); /* 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_NEEDAUTH: if (!triedauth) { nntp_doauth(); triedauth++; goto retrymode; } break; /* will probably fail hard later */ case ERR_COMMAND: default: /* if it doesn't understand MODE READER, we dont care.. :-) */ break; } if (!can_post && !triedauth) { /* * sometimes servers say no posting until auth done, so try. it might * be better if this was controlled by some variable, since some * people are OK with readonly access */ msg("No post, so trying authorization; if readonly OK, just skip"); nntp_doauth(); triedauth++; goto retrymode; } if (who_am_i != I_AM_MASTER && !silent && !reconnecting) msg("Connecting to NNTP server %s ... ok (%s)", nntp_server, can_post ? "posting is allowed" : "no posting"); is_connected = 1; nntp_failed = 0; return 0; } /* * reconnect_server -- reconnect to server after a timeout. * Re-establish correct group and resend last command to get back to the * correct state. * * Returns: -1 if the error is fatal (and we should exit). * # of retries left otherwise. * * Side Effects: If fails to connect and retry==0, calls nn_exitmsg. */ static int reconnect_server(int retry) { char buf[NNTP_STRLEN]; retry--; msg("Reconnecting"); if (nntp_debug) { msg("retry = %d", retry); user_delay(2); } reconnecting = 1; strcpy(buf, last_put); nntp_close_server(); if (connect_server() < 0) { if (nntp_debug) debug_msg("failed to connect", ""); if (retry > 0) return (retry); else nn_exitmsg(1, "failed to reconnect to server"); } if (group_hd) if (nntp_set_group(group_hd) < 1) nn_exitmsg(1, "unable to set group"); if (nntp_debug) debug_msg(" Resend last command", buf); put_server(buf); reconnecting = 0; return (retry); } /* * put_server: send a line to the nntp server. * * Expects to be connected to the server. */ static void put_server(char *string) { if (nntp_debug) debug_msg(">>>", string); strcpy(last_put, string); fprintf(nntp_out, "%s\r\n", string); (void) fflush(nntp_out); return; } /* * 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; static char ask_reply[NNTP_STRLEN]; static int ask_server(char *fmt,...) { char ask[NNTP_STRLEN]; int response; int fix_err; va_list ap; fix_err = fix_list_response; fix_list_response = 0; va_start(ap, fmt); vsprintf(ask, fmt, ap); va_end(ap); put_server(ask); response = get_server(ask_reply, sizeof(ask_reply)); /* * 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. 480 (NEED_AUTH) handle specially. Done here * because some servers can send it at any point, after an idle period, * as well as at various points during connect. 500-599 Fatal NNTP * errors. Handled below. */ if (response == ERR_TIMEOUT) { (void) reconnect_server(1); response = get_server(ask_reply, sizeof(ask_reply)); } if (response == ERR_NEEDAUTH) { /* * try auth, but just once, at least for now, and then retry the * original command, and continue */ nntp_doauth(); put_server(ask); response = get_server(ask_reply, sizeof(ask_reply)); } if (response == ERR_GOODBYE || response > ERR_COMMAND) { if (fix_err && (response == ERR_FAULT || response == ERR_CMDSYN)) return ERR_NOGROUP; nntp_failed = 1; nntp_close_server(); if (response != ERR_GOODBYE) { /* if not goodbye, complain */ sys_error("NNTP %s response: %d", ask_reply, response); /* NOTREACHED */ } if (response == ERR_GOODBYE) { sys_warning("NNTP %s response: %d", ask_reply, response); } } 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 int copy_text(register FILE * fp) { char buf[NNTP_STRLEN * 2]; 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; } /* * 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(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(void) { register struct cache *cptr = cache; int i, lru = 0; 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(void) { struct cache *cptr = cache; int i; for (i = 0; i < nntp_cache_size; i++, cptr++) if (cptr->file_name) unlink(cptr->file_name); } /* * set_domain: set the domain name */ static void set_domain(void) { #if !defined(DOMAIN) || !defined(HIDDENNET) char *cp; #endif #ifdef DOMAIN #ifdef HIDDENNET strncpy(domain, DOMAIN, MAXHOSTNAMELEN); #else strcpy(domain, host_name); cp = index(domain, '.'); if (cp == NULL) { strcat(domain, "."); strncat(domain, DOMAIN, MAXHOSTNAMELEN - sizeof(host_name) - 1); } #endif /* HIDDENNET */ #else /* DOMAIN */ /* * if domain is defined in DOMAIN_FILE, use it */ cp = find_domain(DOMAIN_FILE); if (cp) { strncpy(domain, cp, MAXHOSTNAMELEN); domain[MAXHOSTNAMELEN-1] = 0; /* ensure nul-terminated */ return; } domain[0] = '\0'; cp = index(host_name, '.'); if (cp == NULL) { FILE *resolv; if ((resolv = fopen("/etc/resolv.conf", "r")) != NULL) { char line[MAXHOSTNAMELEN + 1]; char *p; line[MAXHOSTNAMELEN] = '\0'; while (fgets(line, MAXHOSTNAMELEN, resolv)) { if (strncmp(line, "domain", 6) == 0) { for (p = &line[6]; *p && isspace(*p); p++); (void) strncpy(domain, p, MAXHOSTNAMELEN); p = domain + strlen(domain) - 1; while ((p >= domain) && (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n')) p--; *++p = '\0'; break; } } } if (domain[0] == '\0') nn_exitmsg(1, "hostname=%s, You need a fully qualified domain name", host_name); } else { #ifdef HIDDENNET strncpy(domain, ++cp, MAXHOSTNAMELEN); #else strncpy(domain, host_name, MAXHOSTNAMELEN); #endif } #endif /* DOMAIN */ domain[MAXHOSTNAMELEN-1] = 0; /* ensure nul-terminated */ } /* * 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. */ void nntp_check(void) { char *server_real_name = NULL; struct hostent *hp; nn_gethostname(host_name, sizeof host_name); set_domain(); last_put[0] = '\0'; if (nntp_local_server) return; find_server(); if ((hp = gethostbyname(host_name)) != NULL) strncpy(host_name, hp->h_name, sizeof host_name); if ((hp = gethostbyname(nntp_server)) != NULL) server_real_name = hp->h_name; else nn_exitmsg(1, "NNTPSERVER is invalid"); use_nntp = (strcmp(host_name, 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_set_group -- set the server's current group. * * Parameters: gh is the structure containing the group name. * * Returns: 1 if OK. * -1 if no such group. * 0 otherwise. */ int nntp_set_group(group_header * gh) { int n; switch (n = ask_server("GROUP %s", gh->group_name)) { case OK_GROUP: group_hd = gh; return 1; case ERR_NOGROUP: log_entry('N', "NNTP: group %s not found\n", gh->group_name); return -1; default: /* ??? returns this if connection down */ log_entry('N', "GROUP %s response: %d", group_hd->group_name, n); nntp_failed = 1; return -1; /* What did it return? */ } } #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. */ int nntp_get_active(void) { 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); 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: 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(void) { 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; 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: 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 int sort_art_list(register article_number * f1, register article_number * f2) { return (*f1 < *f2) ? -1 : (*f1 == *f2) ? 0 : 1; } article_number * nntp_get_article_list(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; 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: log_entry('N', "LISTGROUP response: %d", n); return NULL; case ERR_COMMAND: try_listgroup = 0; } } if (!try_listgroup) { switch (n = ask_server("XHDR message-id %ld-%ld", (long) gh->first_db_article, (long) gh->last_db_article)) { case OK_HEAD: break; default: 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_number article, int mode) /* mode: 0 => whole article, 1 => head only, 2 => body only */ { FILE *tmp; static struct cache *cptr; int n; if (!is_connected && connect_server() < 0) { return NULL; } /* * 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, 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: /* 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); */ nn_exitmsg(1, "Cannot write temporary file %s", cptr->file_name); return NULL; /* for lint, we can't actually get here */ } /* * Return local file name holding article */ char * nntp_get_filename(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(void) { if (!is_connected) return; if (!nntp_failed) /* avoid infinite recursion */ put_server("QUIT"); (void) fclose(nntp_out); (void) fclose(nntp_in); is_connected = 0; } /* * nntp_cleanup: clean up after an nntp session. * * Called from nn_exit(). */ void nntp_cleanup(void) { if (is_connected) nntp_close_server(); is_connected = 0; 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(group_header * gh, article_number first, article_number last) { int n; if (!is_connected && connect_server() < 0) { return NULL; } switch (nntp_set_group(gh)) { case -1: case 0: return NULL; } n = ask_server("XOVER %d-%d", first, last); switch (n) { case OK_NOV: return novstream(nntp_in); default: 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(char *cmd) { int n; 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: 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(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 */ /* do all the AUTH stuff; used from 3 places that I've found where * various news servers ask for authentication. Given that we exit * on failed auth, probably should just be void. * OLSON: oops, loops on empty user input "forever". Should fix */ static void nntp_doauth(void) { int len; char line[NNTP_STRLEN], *nl, *pass = NULL; strcpy(line, "authinfo user "); len = strlen(line); log_entry('N', "NNTP Authentication"); if (nntp_user) { msg("NNTP authentication"); strcpy(line + len, nntp_user); } else { init_term(1); clrdisp(); prompt("Authentication required; enter username: "); (void) fgets(line + len, sizeof(line) - len, stdin); if ((nl = strpbrk(line, "\r\n"))) *nl = '\0'; nntp_user = strdup(line + len); } put_server(line); if ((len = get_server(line, sizeof(line))) == OK_NEEDPASS) { strcpy(line, "authinfo pass "); len = strlen(line); if (nntp_password) strcpy(line + len, nntp_password); else { #ifdef USEGETPASSPHRASE pass = getpassphrase("Enter password: "); #else pass = getpass("Enter password: "); #endif strcat(line, pass); } put_server(line); if ((len = get_server(line, sizeof(line))) == OK_AUTH) { if (!nntp_password) { nntp_password = strdup(pass); clrdisp(); msg("Remembering password for reconnections"); } } else { nn_exitmsg(1, "Authentication failed (%d): %s", len, line); } } need_auth = 1; } /* ZZZZZ */ /* * The rest of this is cribbed (and modified) from the old mini-inews. * * Simply accept input on stdin (or via a named file) and dump this * to the server; add a From: and Path: line if missing in the original. * Print meaningful errors from the server. * Limit .signature files to MAX_SIGNATURE lines. * * Original by Steven Grady , with thanks from * Phil Lapsley */ /* * gen_frompath -- generate From: and Path: lines, in the form * * From: Full Name * Path: host!user * * This routine should only be called if the message doesn't have * a From: line in it. */ static void gen_frompath(void) { struct passwd *passwd; passwd = getpwuid(getuid()); fprintf(nntp_out, "From: "); fprintf(nntp_out, "%s ", full_name()); fprintf(nntp_out, "<%s@%s>\r\n", passwd->pw_name, domain); #ifdef HIDDENNET /* Only the login name - nntp server will add uucp name */ fprintf(nntp_out, "Path: %s\r\n", passwd->pw_name); #else /* HIDDENNET */ fprintf(nntp_out, "Path: %s!%s\r\n", host_name, passwd->pw_name); #endif /* HIDDENNET */ } /* * lower -- convert a character to lower case, if it's * upper case. * * Parameters: "c" is the character to be * converted. * * Returns: "c" if the character is not * upper case, otherwise the lower * case eqivalent of "c". * * Side effects: None. */ static char lower(register char c) { if (isascii(c) && isupper(c)) c = c - 'A' + 'a'; return (c); } /* * strneql -- determine if two strings are equal in the first n * characters, ignoring case. * * Parameters: "a" and "b" are the pointers * to characters to be compared. * "n" is the number of characters to compare. * * Returns: 1 if the strings are equal, 0 otherwise. * * Side effects: None. */ static int strneql(register char *a, register char *b, int n) { while (n && lower(*a) == lower(*b)) { if (*a == '\0') return (1); a++; b++; n--; } if (n) return (0); else return (1); } /* * valid_header -- determine if a line is a valid header line * * Parameters: "h" is the header line to be checked. * * Returns: 1 if valid, 0 otherwise * * Side Effects: none * */ static int valid_header(register char *h) { char *colon, *space; /* * blank or tab in first position implies this is a continuation header */ if (h[0] == ' ' || h[0] == '\t') return (1); /* * just check for initial letter, colon, and space to make sure we * discard only invalid headers */ colon = index(h, ':'); space = index(h, ' '); if (isalpha(h[0]) && colon && space == colon + 1) return (1); /* * anything else is a bad header -- it should be ignored */ return (0); } int nntp_post(char *temp_file) { int n, response; FILE *in = fopen(temp_file, "r"); char s[4 * NNTP_STRLEN]; int seen_fromline, in_header, seen_header; register char *cp; if (!in) { sprintf(delayed_msg, "Posting failed because we couldn't re-open file %s.", temp_file); return 1; } if (!is_connected && connect_server() < 0) return 1; switch (n = ask_server("POST")) { case CONT_POST: break; default: sprintf(delayed_msg, "Request to post failed with error %d, %s", n, ask_reply); fclose(in); return 1; } in_header = 1; seen_header = 0; seen_fromline = 0; while (fgets(s, sizeof(s), in) != NULL) { if ((cp = strchr(s, '\n'))) *cp = '\0'; if (s[0] == '.') /* Single . is eof, so put in extra one */ (void) fputc('.', nntp_out); if (in_header && strneql(s, "From:", sizeof("From:") - 1)) { seen_header = 1; seen_fromline = 1; } if (in_header && s[0] == '\0') { if (seen_header) { in_header = 0; if (!seen_fromline) gen_frompath(); fprintf(nntp_out, "User-Agent: nn/%s.%s\r\n", RELEASE, PATCHLEVEL); } else { continue; } } else if (in_header) { if (valid_header(s)) seen_header = 1; else continue; } fprintf(nntp_out, "%s\r\n", s); } fprintf(nntp_out, ".\r\n"); (void) fflush(nntp_out); response = get_server(ask_reply, sizeof(ask_reply)); switch (response) { case OK_POSTED: break; case ERR_POSTFAIL: msg("Article not accepted by server; not posted."); user_delay(2); return 1; default: msg("Remote error: %s", ask_reply); user_delay(2); return 1; } return 0; } #ifdef NeXT /* * posted to comp.sys.next.programmer: * * * From: moser@ifor.math.ethz.ch (Dominik Moser,CLV A4,2 40 19,720 49 89) * Subject: Re: Compile problems (pgp 2.6.3i) * Date: 10 Jul 1996 06:50:42 GMT * Organization: Swiss Federal Institute of Technology (ETHZ) * References: <4rrhvj$6fr@bagan.srce.hr> * Message-ID: <4rvjs2$6oh@elna.ethz.ch> * * Most systems don't have this (yet) */ static char * strdup(char *str) { char *p; if ((p = malloc(strlen(str) + 1)) == NULL) return ((char *) NULL); (void) strcpy(p, str); return (p); } #endif /* NeXT */ #endif /* NNTP */