/* This file is part of SLRN. Copyright (c) 1994, 1999 John E. Davis This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "config.h" #include #include #ifdef HAVE_STDLIB_H # include #endif #ifdef HAVE_UNISTD_H # include #endif #include #include #include "jdmacros.h" #include "sltcp.h" #include "nntpcodes.h" #include "nntplib.h" #include "util.h" #include "ttymsg.h" void (*NNTP_Connection_Lost_Hook) (NNTP_Type *); int (*NNTP_Authorization_Hook) (char *, char **, char **); FILE *NNTP_Debug_Fp; static int _nntp_connect_server (NNTP_Type *); static NNTP_Type *nntp_allocate_nntp (void) { static NNTP_Type nn; NNTP_Type *s; s = &nn; memset ((char *) s, 0, sizeof (NNTP_Type)); s->can_xover = -1; s->can_xhdr = -1; s->can_xpat = -1; s->can_xgtitle = -1; return s; } static void _nntp_deallocate_nntp (NNTP_Type *s) { if (s == NULL) return; memset ((char *) s, 0, sizeof (NNTP_Type)); } /* Call connection-lost hook, unless it is ok to reconnect */ static int check_connect_lost_hook (NNTP_Type *s) { if (s->flags & NNTP_RECONNECT_OK) return 0; if (NNTP_Connection_Lost_Hook != NULL) (*NNTP_Connection_Lost_Hook) (s); return -1; } int nntp_fgets_server (NNTP_Type *s, char *buf, unsigned int len) { if ((s == NULL) || (s->init_state <= 0)) return -1; *buf = 0; if (-1 == sltcp_fgets (s->tcp, buf, len)) { (void) nntp_disconnect_server (s); (void) check_connect_lost_hook (s); return -1; } if (NNTP_Debug_Fp != NULL) fprintf (NNTP_Debug_Fp, "<%s", buf); return 0; } int nntp_fputs_server (NNTP_Type *s, char *buf) { if ((s == NULL) || (s->init_state == 0)) return -1; if (NNTP_Debug_Fp != NULL) fprintf (NNTP_Debug_Fp, ">%s\n", buf); if (-1 == sltcp_fputs (s->tcp, buf)) { (void) nntp_disconnect_server (s); (void) check_connect_lost_hook (s); return -1; } return 0; } int nntp_write_server (NNTP_Type *s, char *buf, unsigned int n) { if ((s == NULL) || (s->init_state == 0)) return -1; if (NNTP_Debug_Fp != NULL) { unsigned int i; fputc ('>', NNTP_Debug_Fp); for (i = 0; i < n; i++) fputc (buf[i], NNTP_Debug_Fp); fputc ('\n', NNTP_Debug_Fp); } if (n != sltcp_write (s->tcp, buf, n)) { (void) nntp_disconnect_server (s); (void) check_connect_lost_hook (s); return -1; } return 0; } int nntp_gets_server (NNTP_Type *s, char *buf, unsigned int len) { if (-1 == nntp_fgets_server (s, buf, len)) return -1; len = strlen (buf); if (len && (buf[len - 1] == '\n')) { len--; buf[len] = 0; if (len && (buf[len - 1] == '\r')) buf[len - 1] = 0; } return 0; } int nntp_puts_server (NNTP_Type *s, char *buf) { if ((-1 == nntp_fputs_server (s, buf)) || (-1 == nntp_fputs_server (s, "\r\n")) || (-1 == sltcp_flush_output (s->tcp))) { (void) check_connect_lost_hook (s); nntp_disconnect_server (s); return -1; } return 0; } static void _nntp_error_response (NNTP_Type *s, char *fmt) { slrn_error ("%s", fmt); slrn_error ("Reason: %s", s->rspbuf); } static int _nntp_try_parse_timeout (char *str) { /* I know of only two timeout responses: * 503 Timeout * 503 connection timed out * * Here the idea is to look for 'time' and then 'out'. */ static SLRegexp_Type re; unsigned char compiled_pattern_buf[256]; re.pat = (unsigned char *) "time.*out"; re.buf = compiled_pattern_buf; re.case_sensitive = 0; re.buf_len = sizeof (compiled_pattern_buf); (void) SLang_regexp_compile (&re); if (NULL == SLang_regexp_match ((unsigned char *) str, strlen (str), &re)) return -1; return 0; } int nntp_get_server_response (NNTP_Type *s) { int status; if ((s == NULL) || (s->init_state <= 0)) return -1; if (-1 == nntp_gets_server (s, s->rspbuf, NNTP_RSPBUF_SIZE)) return -1; status = atoi (s->rspbuf); switch (status) { case ERR_FAULT: if (-1 == _nntp_try_parse_timeout (s->rspbuf)) break; /* Drop */ case 0: /* invalid code */ case ERR_GOODBYE: nntp_disconnect_server (s); (void) check_connect_lost_hook (s); status = -1; break; } s->code = status; return status; } static int _nntp_reconnect (NNTP_Type *s) { nntp_disconnect_server (s); if (-1 == check_connect_lost_hook (s)) return -1; s->flags &= ~NNTP_RECONNECT_OK; slrn_message_now ("Server %s dropped connection. Reconnecting...", s->host); if (-1 == _nntp_connect_server (s)) { (void) check_connect_lost_hook (s); s->flags |= NNTP_RECONNECT_OK; return -1; } if ((s->group_name[0] != 0) && (-1 == nntp_server_vcmd (s, "GROUP %s", s->group_name))) { (void) check_connect_lost_hook (s); s->flags |= NNTP_RECONNECT_OK; return -1; } s->flags |= NNTP_RECONNECT_OK; return 0; } int nntp_start_server_cmd (NNTP_Type *s, char *cmd) { int max_tries = 3; if (s == NULL) return -1; while ((s->init_state != 0) && max_tries && (SLKeyBoard_Quit == 0)) { if (-1 != nntp_puts_server (s, cmd)) return 0; if (-1 == _nntp_reconnect (s)) return -1; max_tries--; } return -1; } int nntp_server_cmd (NNTP_Type *s, char *cmd) { int max_tries = 3; do { int code; if (-1 == nntp_start_server_cmd (s, cmd)) return -1; if (-1 != (code = nntp_get_server_response (s))) return code; max_tries--; } while (max_tries && (s->flags & NNTP_RECONNECT_OK)); return -1; } int nntp_server_vcmd (NNTP_Type *s, char *fmt, ...) { char buf [NNTP_MAX_CMD_LEN]; va_list ap; va_start (ap, fmt); vsprintf (buf, fmt, ap); va_end (ap); return nntp_server_cmd (s, buf); } int nntp_start_server_vcmd (NNTP_Type *s, char *fmt, ...) { char buf [NNTP_MAX_CMD_LEN]; va_list ap; va_start (ap, fmt); vsprintf (buf, fmt, ap); va_end (ap); return nntp_start_server_cmd (s, buf); } int nntp_close_server (NNTP_Type *s) { if (s == NULL) return -1; if (s->init_state <= 0) return 0; s->init_state = -1; /* closing */ /* This might be called from a connect_lost hook. We also do not * want to reconnect to send the QUIT command so do not call any * of the *server_cmd functions. */ (void) nntp_puts_server (s, "QUIT"); (void) sltcp_close (s->tcp); s->tcp = NULL; s->init_state = 0; _nntp_deallocate_nntp (s); return 0; } static int _nntp_authorization (NNTP_Type *s) { char *name, *pass; if (NNTP_Authorization_Hook == NULL) return 0; if (-1 == (*NNTP_Authorization_Hook) (s->host, &name, &pass)) return -1; if ((name == NULL) || (pass == NULL)) return 0; slrn_message_now ("Authenticating %s ...", name); if (-1 == nntp_server_vcmd (s, "AUTHINFO USER %s", name)) return -1; if (s->code != NEED_AUTHDATA) return 0; switch (nntp_server_vcmd (s, "AUTHINFO PASS %s", pass)) { case -1: return -1; case ERR_ACCESS: _nntp_error_response (s, "Authorization failed."); return -1; case OK_AUTH: s->can_post = 1; break; } return 0; } static int _nntp_connect_server (NNTP_Type *s) { if (s->tcp != NULL) sltcp_close (s->tcp); slrn_message_now ("Connecting to host %s ...", s->host); if (NULL == (s->tcp = sltcp_open_connection (s->host, s->port))) return -1; s->init_state = 1; /* Read logon message. */ switch (nntp_get_server_response (s)) { case OK_CANPOST: s->can_post = 1; break; case OK_NOPOST: s->can_post = 0; break; default: goto failed; } if ((-1 == nntp_server_cmd (s, "MODE READER")) || (ERR_ACCESS == s->code) || (-1 == _nntp_authorization (s))) goto failed; slrn_message_now ("Connected to host. Posting %sOk.", (s->can_post ? "" : "NOT ")); return 0; failed: _nntp_error_response (s, "Failed to initialize server"); (void) sltcp_close (s->tcp); s->tcp = NULL; return -1; } #ifndef NNTPSERVER_FILE # define NNTPSERVER_FILE NULL #endif static char *_nntp_getserverbyfile (char *file) { FILE *fp; char *host; static char buf[256]; host = getenv("NNTPSERVER"); if (host != NULL) { strcpy (buf, host); return buf; } if (file == NULL) { #ifdef NNTPSERVER_NAME strcpy (buf, NNTPSERVER_NAME); return buf; #else return NULL; #endif } if (NULL == (fp = fopen(file, "r"))) return NULL; while (NULL != fgets(buf, sizeof (buf), fp)) { char *b; b = slrn_skip_whitespace (buf); if ((*b == 0) || (*b == '#')) continue; slrn_trim_string (b); (void) fclose(fp); return b; } (void) fclose(fp); return NULL; /* No entry */ } char *nntp_get_server_name (void) { char *host; if (NULL != (host = _nntp_getserverbyfile(NNTPSERVER_FILE))) return host; fprintf (stderr, "You need to set the NNTPSERVER environment variable to your server name.\r\n"); #ifdef VMS fprintf (stderr, "Example: $ define/job NNTPSERVER my.news.server\r\n"); #else # if defined(IBMPC_SYSTEM) fprintf (stderr, "Example: set NNTPSERVER=my.news.server\r\n"); # else fprintf (stderr, "Example (csh): setenv NNTPSERVER my.news.server\r\n"); # endif #endif return NULL; } /* In general, host has the form: "address:port". If port is non-negative * use its value despite the value coded in the hostname. */ static int _nntp_setup_host_and_port (char *host, int port, NNTP_Type *s) { char *a, *b, *bmax; a = host; b = s->host; bmax = b + NNTP_MAX_HOST_LEN; while ((*a != 0) && (*a != ':') && (b < bmax)) *b++ = *a++; *b = 0; if (port <= 0) { if (*a == ':') port = atoi (a + 1); else if (-1 == (port = sltcp_map_service_to_port ("nntp"))) port = 119; } s->port = port; return 0; } NNTP_Type *nntp_open_server (char *host, int port) { NNTP_Type *s; #if 0 if (NNTP_Debug_Fp == NULL) NNTP_Debug_Fp = fopen ("nntp.log", "w"); #endif if (host == NULL) { host = nntp_get_server_name (); if (host == NULL) return NULL; } if (NULL == (s = nntp_allocate_nntp ())) return NULL; (void) _nntp_setup_host_and_port (host, port, s); if (-1 == _nntp_connect_server (s)) { _nntp_deallocate_nntp (s); return NULL; } return s; } int nntp_read_line (NNTP_Type *s, char *buf, unsigned int len) { if (-1 == nntp_gets_server (s, buf, len)) return -1; if ((buf[0] == '.') && (buf[1] == 0)) return 0; return 1; } int nntp_discard_output (NNTP_Type *s) { char buf [NNTP_BUFFER_SIZE]; int status; while (1 == (status = nntp_read_line (s, buf, sizeof (buf)))) continue; return status; } int nntp_reconnect_server (NNTP_Type *s) { unsigned int flag; int status; if (s == NULL) return -1; nntp_disconnect_server (s); flag = s->flags & NNTP_RECONNECT_OK; s->flags |= NNTP_RECONNECT_OK; status = _nntp_reconnect (s); if (flag == 0) s->flags &= ~NNTP_RECONNECT_OK; return status; } int nntp_check_connection (NNTP_Type *s) { if ((s == NULL) || (s->tcp == NULL) || (s->tcp->tcp_fd == -1)) return -1; return 0; } void nntp_disconnect_server (NNTP_Type *s) { if (s == NULL) return; sltcp_close_socket (s->tcp); } static int _nntp_probe_server (NNTP_Type *s, char *cmd) { if (-1 == nntp_server_cmd (s, cmd)) return -1; if (ERR_COMMAND == s->code) return 0; return 1; } #define PROBE_XCMD(s, var,cmd) (((var) != -1) ? (var) : ((var) = _nntp_probe_server ((s),(cmd)))) int nntp_has_cmd (NNTP_Type *s, char *cmd) { /* DNEWS, in my opinion does not handle XHDR probing properly. * Sigh. */ if (!strcmp (cmd, "XHDR")) { int ret; if (s->can_xhdr != -1) return s->can_xhdr; if (-1 == (ret = nntp_server_cmd (s, cmd))) return -1; if (ret == ERR_COMMAND) { s->can_xhdr = 0; return 0; } s->can_xhdr = 1; if (ret == OK_HEAD) /* DNEWS bug?? */ (void) nntp_discard_output (s); return 1; } if (!strcmp (cmd, "XPAT")) return PROBE_XCMD(s, s->can_xpat, cmd); if (!strcmp (cmd, "XGTITLE")) return PROBE_XCMD(s, s->can_xgtitle, cmd); if (!strcmp (cmd, "XOVER")) return PROBE_XCMD(s, s->can_xover, cmd); return _nntp_probe_server (s, cmd); } static int _nntp_num_or_msgid_cmd (NNTP_Type *s, char *cmd, int n, char *msgid) { if (n != -1) return nntp_server_vcmd (s, "%s %d", cmd, n); else { if (msgid == NULL) msgid = ""; return nntp_server_vcmd (s, "%s %s", cmd, msgid); } } int nntp_head_cmd (NNTP_Type *s, int n, char *msgid, int *real_id) { int status; status = _nntp_num_or_msgid_cmd (s, "HEAD", n, msgid); if ((status == OK_HEAD) && (real_id != NULL)) *real_id = atoi(s->rspbuf + 4); return status; } int nntp_xover_cmd (NNTP_Type *s, int min, int max) { return nntp_server_vcmd (s, "XOVER %d-%d", min, max); } int nntp_article_cmd (NNTP_Type *s, int n, char *msgid) { return _nntp_num_or_msgid_cmd (s, "ARTICLE", n, msgid); } int nntp_next_cmd (NNTP_Type *s, int *n) { int status; if ((OK_NEXT == (status = nntp_server_cmd (s, "NEXT"))) && (n != NULL)) *n = atoi (s->rspbuf + 4); return status; } int nntp_select_group (NNTP_Type *s, char *name, int *minp, int *maxp) { int estim; int min, max; switch (nntp_server_vcmd (s, "GROUP %s", name)) { case -1: return -1; case OK_GROUP: if (3 != sscanf(s->rspbuf + 4, "%d %d %d", &estim, &min, &max)) return -1; if (minp != NULL) *minp = min; if (maxp != NULL) *maxp = max; strcpy (s->group_name, name); break; case ERR_ACCESS: default: break; } return s->code; } int nntp_post_cmd (NNTP_Type *s) { return _nntp_num_or_msgid_cmd (s, "POST", -1, NULL); } int nntp_end_post (NNTP_Type *s) { if (-1 == nntp_puts_server (s, ".")) return -1; return nntp_get_server_response (s); } int nntp_xpat_cmd (NNTP_Type *s, char *hdr, int rmin, int rmax, char *pat) { if (0 == PROBE_XCMD(s, s->can_xpat, "XPAT")) return ERR_COMMAND; return nntp_server_vcmd (s, "XPAT %s %d-%d *%s*", hdr, rmin, rmax, pat); } int nntp_xgtitle_cmd (NNTP_Type *s, char *pattern) { if (s->can_xgtitle == 0) return -1; /* XGTITLE appears broken on some servers. So, do not probe for it. */ slrn_message_now ("Sending Query to Server ..."); if (-1 == nntp_server_vcmd (s, "XGTITLE %s", pattern)) return -1; if (s->code != OK_XGTITLE) { slrn_message ("Server does not support XGTITLE command."); s->can_xgtitle = 0; return ERR_COMMAND; } s->can_xgtitle = 1; return OK_XGTITLE; } /* hdr should not include ':' */ int nntp_xhdr_cmd (NNTP_Type *s, char *hdr, int num, char *buf, unsigned int buflen) { char tmpbuf[1024]; int found; unsigned int colon; int status; status = PROBE_XCMD(s, s->can_xhdr, "XHDR"); if (status == -1) return -1; if (status == 1) { char *b, ch; if (-1 == nntp_server_vcmd (s, "XHDR %s %d", hdr, num)) return -1; if (OK_HEAD != s->code) { /* It comes as no surprise that Micro$oft apparantly makes * buggy servers too. Sigh. */ if (s->code != 224) return -1; s->code = OK_HEAD; } status = nntp_read_line (s, tmpbuf, sizeof(tmpbuf)); if (status != 1) return -1; /* skip past article number */ b = tmpbuf; while (((ch = *b++) >= '0') && (ch <= '9')) ; strncpy (buf, b, buflen - 1); buf[buflen - 1] = 0; /* I should handle multi-line returns but I doubt that there will be * any for our use of xhdr */ (void) nntp_discard_output (s); return 0; } /* Server does not have XHDR so, use HEAD */ if (-1 == nntp_head_cmd (s, num, NULL, NULL)) return -1; if (s->code != OK_HEAD) return -1; found = 0; colon = strlen (hdr); while (1 == (status = nntp_read_line (s, tmpbuf, sizeof (tmpbuf)))) { char *b; if (found || slrn_case_strncmp ((unsigned char *) tmpbuf, (unsigned char *) hdr, colon) || (tmpbuf[colon] != ':')) continue; found = 1; b = tmpbuf + (colon + 1); /* skip past colon */ if (*b == ' ') b++; strncpy (buf, b, buflen - 1); buf[buflen - 1] = 0; } return status; } int nntp_list_newsgroups (NNTP_Type *s) { return _nntp_num_or_msgid_cmd (s, "LIST", -1, "NEWSGROUPS"); } int nntp_list_active_cmd (NNTP_Type *s) { return _nntp_num_or_msgid_cmd (s, "LIST", -1, NULL); } int nntp_listgroup (NNTP_Type *s, char *group) { return nntp_server_vcmd (s, "LISTGROUP %s", group); /* OK_GROUP desired */ } int nntp_body_cmd (NNTP_Type *s, int n, char *msgid) { return _nntp_num_or_msgid_cmd (s, "BODY", n, msgid); } char *nntp_read_and_malloc (NNTP_Type *s) { char line [NNTP_BUFFER_SIZE]; char *mbuf; unsigned int buffer_len, buffer_len_max; int status; mbuf = NULL; buffer_len_max = buffer_len = 0; while (1 == (status = nntp_read_line (s, line, sizeof(line)))) { unsigned int len; len = strlen (line); if (len + buffer_len + 4 > buffer_len_max) { char *new_mbuf; buffer_len_max += 4096 + len; new_mbuf = slrn_realloc (mbuf, buffer_len_max, 0); if (new_mbuf == NULL) { slrn_free (mbuf); nntp_discard_output (s); return NULL; } mbuf = new_mbuf; } strcpy (mbuf + buffer_len, line); buffer_len += len; mbuf [buffer_len++] = '\n'; mbuf [buffer_len] = 0; } if (status == 0) return mbuf; slrn_free (mbuf); return NULL; } char *nntp_map_code_to_string (int code) { switch (code) { case INF_HELP: /* 100 */ return "Help text on way"; case INF_AUTH: /* 180 */ return "Authorization capabilities"; case INF_DEBUG: /* 199 */ return "Debug output"; case OK_CANPOST: /* 200 */ return "Hello; you can post"; case OK_NOPOST: /* 201 */ return "Hello; you can't post"; case OK_SLAVE: /* 202 */ return "Slave status noted"; case OK_GOODBYE: /* 205 */ return "Closing connection"; case OK_GROUP: /* 211 */ return "Group selected"; case OK_GROUPS: /* 215 */ return "Newsgroups follow"; case OK_ARTICLE: /* 220 */ return "Article (head & body) follows"; case OK_HEAD: /* 221 */ return "Head follows"; case OK_BODY: /* 222 */ return "Body follows"; case OK_NOTEXT: /* 223 */ return "No text sent -- stat, next, last"; case OK_NEWNEWS: /* 230 */ return "New articles by message-id follow"; case OK_NEWGROUPS:/* 231 */ return "New newsgroups follow"; case OK_XFERED: /* 235 */ return "Article transferred successfully"; case OK_POSTED: /* 240 */ return "Article posted successfully"; case OK_AUTHSYS: /* 280 */ return "Authorization system ok"; case OK_AUTH: /* 281 */ return "Authorization (user/pass) ok"; case OK_XGTITLE: /* 282 */ return "Ok, XGTITLE info follows"; case OK_XOVER: /* 224 */ return "ok -- overview data follows"; case CONT_XFER: /* 335 */ return "Continue to send article"; case CONT_POST: /* 340 */ return "Continue to post article"; case NEED_AUTHINFO:/* 380 */ return "authorization is required"; case NEED_AUTHDATA:/* 381 */ return " authorization data required"; case ERR_GOODBYE: /* 400 */ return "Have to hang up for some reason"; case ERR_NOGROUP: /* 411 */ return "No such newsgroup"; case ERR_NCING: /* 412 */ return "Not currently in newsgroup"; case ERR_NOCRNT: /* 420 */ return "No current article selected"; case ERR_NONEXT: /* 421 */ return "No next article in this group"; case ERR_NOPREV: /* 422 */ return "No previous article in this group"; case ERR_NOARTIG: /* 423 */ return "No such article in this group"; case ERR_NOART: /* 430 */ return "No such article at all"; case ERR_GOTIT: /* 435 */ return "Already got that article, don't send"; case ERR_XFERFAIL:/* 436 */ return "Transfer failed"; case ERR_XFERRJCT:/* 437 */ return "Article rejected, don't resend"; case ERR_NOPOST: /* 440 */ return "Posting not allowed"; case ERR_POSTFAIL:/* 441 */ return "Posting failed"; case ERR_NOAUTH: /* 480 */ return "authorization required for command"; case ERR_AUTHSYS: /* 481 */ /* ERR_XGTITLE has same code */ return "Authorization system invalid"; case ERR_AUTHREJ: /* 482 */ return "Authorization data rejected"; case ERR_COMMAND: /* 500 */ return "Command not recognized"; case ERR_CMDSYN: /* 501 */ return "Command syntax error"; case ERR_ACCESS: /* 502 */ return "Access to server denied"; case ERR_FAULT: /* 503 */ return "Program fault, command not performed"; case ERR_AUTHBAD: /* 580 */ return "Authorization Failed"; } return "Unknown NNTP code"; }