/* ** Copyright (c) 2001 The OSSP Project ** Copyright (c) 2001 Cable & Wireless Deutschland ** ** This file is part of OSSP lmtp2nntp, an LMTP speaking local ** mailer which forwards mails as Usenet news articles via NNTP. ** It can be found at http://www.ossp.org/pkg/lmtp2nntp/. ** ** 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.0 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 file; if not, write to the Free Software ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ** USA, or contact the OSSP project . ** ** nntp.c: Network News Transfer Protocol (NNTP) client library */ #include #include #include #include #include #include #include #include #include "nntp.h" #ifdef HAVE_CONFIG_H #include "config.h" #endif #if defined(HAVE_DMALLOC_H) && defined(DMALLOC) #include "dmalloc.h" #endif #ifndef FALSE #define FALSE (1 != 1) #endif #ifndef TRUE #define TRUE (!FALSE) #endif #ifndef NUL #define NUL '\0' #endif /* maximum NNTP protocol line length */ #define NNTP_LINE_MAXLEN 1024 typedef struct { int rl_cnt; char *rl_bufptr; char rl_buf[NNTP_LINE_MAXLEN]; } nntp_readline_t; struct nntp_st { nntp_io_t io; nntp_readline_t rl; struct timeval tv; int kludgeinn441dup; char *lastresp; }; ssize_t nntp_fd_read(void *_ctx, void *buf, size_t buflen) { nntp_fd_t *ctx = (nntp_fd_t *)_ctx; return read(ctx->fd, buf, buflen); } ssize_t nntp_fd_write(void *_ctx, const void *buf, size_t buflen) { nntp_fd_t *ctx = (nntp_fd_t *)_ctx; return write(ctx->fd, buf, buflen); } nntp_t *nntp_create(nntp_io_t *io) { nntp_t *nntp; if ((nntp = (nntp_t *)malloc(sizeof(nntp_t))) == NULL) return NULL; if (io == NULL) return NULL; nntp->io.ctx = io->ctx; nntp->io.read = io->read; nntp->io.write = io->write; nntp->rl.rl_cnt = 0; nntp->rl.rl_bufptr = NULL; nntp->rl.rl_buf[0] = NUL; nntp->kludgeinn441dup = FALSE; nntp->lastresp = NULL; return nntp; } nntp_rc_t nntp_init(nntp_t *nntp) { nntp_rc_t rc; char line[NNTP_LINE_MAXLEN]; /* RFC0977 2.4.3. General Responses * In general, 1xx codes may be ignored or displayed as desired; code 200 * or 201 is sent upon initial connection to the NNTP server depending * upon posting permission; code 400 will be sent when the NNTP server * discontinues service (by operator request, for example); and 5xx codes * indicate that the command could not be performed for some unusual * reason. * * 1xx text * 200 server ready - posting allowed * 201 server ready - no posting allowed * 400 service discontinued * 500 command not recognized * 501 command syntax error * 502 access restriction or permission denied * 503 program fault - command not performed */ do { if ((rc = nntp_readline(nntp, line, sizeof(line))) != NNTP_OK) { return rc; } } while (line[0] == '1'); /* prepare for the INN kludge using 441 returns for other things but * "Duplicate". Typical welcome string is "200 host InterNetNews NNRP * server INN 2.3.2 ready (posting ok)." */ if (strncmp(line, "200", 3) == 0 && strstr(line, " INN ") != NULL) { nntp->kludgeinn441dup = TRUE; } else { nntp->kludgeinn441dup = FALSE; } if (strncmp(line, "200", 3) == 0) return NNTP_OK; return NNTP_ERR_INIT; } void nntp_destroy(nntp_t *nntp) { if (nntp != NULL) { if (nntp->lastresp != NULL) free(nntp->lastresp); free(nntp); } return; } char *nntp_lastresp(nntp_t *nntp) { if (nntp == NULL) return ""; if(nntp->lastresp == NULL) return ""; return nntp->lastresp; } nntp_rc_t nntp_readline(nntp_t *nntp, char *buf, size_t buflen) { /* read a line (characters until NL) from input stream */ size_t n; char c; nntp_readline_t *rl = &nntp->rl; if (nntp == NULL) return NNTP_ERR_ARG; for (n = 0; n < buflen-1;) { /* fetch one character (but read more) */ if (rl->rl_cnt <= 0) { do { rl->rl_cnt = nntp->io.read(nntp->io.ctx, rl->rl_buf, NNTP_LINE_MAXLEN); } while (rl->rl_cnt == -1 && errno == EINTR); if (rl->rl_cnt == -1) return NNTP_ERR_SYSTEM; if (rl->rl_cnt == 0) return NNTP_EOF; rl->rl_bufptr = rl->rl_buf; } /* act on fetched character */ rl->rl_cnt--; c = *rl->rl_bufptr++; if (c == '\r') continue; /* skip copying CR */ if (c == '\n') break; /* end of line */ buf[n++] = c; /* output char into given buffer */ } buf[n] = NUL; /* string termination */ if (n == (buflen-1)) return NNTP_ERR_OVERFLOW; return NNTP_OK; } nntp_rc_t nntp_writeline(nntp_t *nntp, char *buf) { char tmp[NNTP_LINE_MAXLEN]; if (nntp == NULL) return NNTP_ERR_ARG; strncpy(tmp, buf, NNTP_LINE_MAXLEN-3); strcat(tmp, "\r\n"); if (nntp->io.write(nntp->io.ctx, tmp, strlen(tmp)) < 0) return NNTP_ERR_SYSTEM; return NNTP_OK; } nntp_rc_t nntp_post(nntp_t *nntp, msg_t *msg) { nntp_rc_t rc = NNTP_OK; char line[NNTP_LINE_MAXLEN]; /* RFC2980 * * 2.3 MODE READER * MODE READER is used by the client to indicate to the server that it is * a news reading client. Some implementations make use of this * information to reconfigure themselves for better performance in * responding to news reader commands. This command can be contrasted * with the SLAVE command in RFC0977, which was not widely implemented. * MODE READER was first available in INN. * * 2.3.1 Responses * 200 Hello, you can post * 201 Hello, you can't post * * Research: * * < 200 dev16 InterNetNews server INN 2.3.2 ready * > POST * < 500 "post" not implemented; try "help". * > MODE READER * < 200 dev16 InterNetNews NNRP server INN 2.3.2 ready (posting ok). * > POST * < 340 Ok, recommended ID <...> * * In other words, INN *requires* the use of "MODE READER". This has been * verified when INN is configured for expecting both news readers and * feeders from a given source address. When INN is configured to expect * readers only for a given source address the use of "MODE READER" is * optional. */ *line = NUL; strcat(line, "MODE READER"); if ((rc = nntp_writeline(nntp, line)) != NNTP_OK) return rc; if ((rc = nntp_readline(nntp, line, sizeof(line))) != NNTP_OK) return rc; /* A 200 response means posting ok, 201 means posting not allowed. We * don't care about 5xx errors, they simply mean the server doesn't know * about the RFC2980 "MODE READER" command. But any other response is not * expected and we treat this as an error. */ if (strncmp(line, "201", 3) == 0) CU(NNTP_ERR_DELIVERY); if ( strncmp(line, "200", 3) != 0 && strncmp(line, "5" , 1) != 0 ) CU(NNTP_ERR_DELIVERY); /* check if this server already knows that artice * > STAT * < 223 yes, article already known * < 430 no, i don't know the article, yet */ *line = NUL; strcat(line, "STAT "); strcat(line, msg->cpMsgid); if ((rc = nntp_writeline(nntp, line)) != NNTP_OK) return rc; if ((rc = nntp_readline(nntp, line, sizeof(line))) != NNTP_OK) return rc; if (strncmp(line, "223", 3) == 0) return NNTP_OK; if (strncmp(line, "430", 3) != 0) CU(NNTP_ERR_DELIVERY); /* post the article * > POST * < 340 gimme that thing * > From: ... * > Subject: ... * > Newsgroups: ... * > Message-ID: <...> * > [additional headers] * > * > [body with dots escaped] * > . * < 240 ok, thank you * < 441 duplicate (ok for us) * * Research: * * < 200 dev16 InterNetNews server INN 2.3.2 ready * > POST * [...] * 240 Article posted <...> * 441 435 Duplicate * 441 437 Too old * 441 Duplicate "Newsgroups" header * 441 Required "Subject" header is missing * 441 Obsolete "Received" header * 441 Line # too long * * In other words, INN uses 441 for other status messages as well. */ *line = NUL; strcat(line, "POST"); if ((rc = nntp_writeline(nntp, line)) != NNTP_OK) return rc; if ((rc = nntp_readline(nntp, line, sizeof(line))) != NNTP_OK) return rc; if (strncmp(line, "340", 3) != 0) CU(NNTP_ERR_DELIVERY); do { rc = nntp->io.write(nntp->io.ctx, msg->cpMsg, strlen(msg->cpMsg)); } while (rc == -1 && errno == EINTR); if (rc == -1) return NNTP_ERR_SYSTEM; if ((rc = nntp_readline(nntp, line, sizeof(line))) != NNTP_OK) return rc; if (strncmp(line, "240", 3) == 0) return NNTP_OK; if (nntp->kludgeinn441dup) { if (strncmp(line, "441 435", 7) == 0) return NNTP_OK; } else { if (strncmp(line, "441", 3) == 0) return NNTP_OK; } CU(NNTP_ERR_DELIVERY); CUS: nntp->lastresp = strdup(line); return rc; } nntp_rc_t nntp_feed(nntp_t *nntp, msg_t *msg) { nntp_rc_t rc = NNTP_OK; char line[NNTP_LINE_MAXLEN]; /* RFC0977 3.4. The IHAVE command, 3.4.2. Responses * < 235 article transferred ok * < 335 send article to be transferred. End with . * < 435 article not wanted - do not send it * < 436 transfer failed - try again later * < 437 article rejected - do not try again * * Research: * < 200 dev16 InterNetNews server INN 2.3.2 ready * > IHAVE * < 335 [no text; this number means positive response] * < 435 Duplicate * < 437 Missing "Path" header * < 437 Unwanted newsgroup "quux" * < 480 Transfer permission denied */ *line = NUL; strcat(line, "IHAVE "); strcat(line, msg->cpMsgid); if ((rc = nntp_writeline(nntp, line)) != NNTP_OK) return rc; if ((rc = nntp_readline(nntp, line, sizeof(line))) != NNTP_OK) return rc; if (strncmp(line, "435", 3) == 0) return NNTP_OK; if (strncmp(line, "436", 3) == 0) return NNTP_DEFER; if ( (strncmp(line, "437", 3) == 0) || (strncmp(line, "480", 3) == 0)) CU(NNTP_ERR_DELIVERY); if (strncmp(line, "335", 3) != 0) CU(NNTP_ERR_DELIVERY); do { rc = nntp->io.write(nntp->io.ctx, msg->cpMsg, strlen(msg->cpMsg)); } while (rc == -1 && errno == EINTR); if (rc == -1) return NNTP_ERR_SYSTEM; if ((rc = nntp_readline(nntp, line, sizeof(line))) != NNTP_OK) return rc; if (strncmp(line, "235", 3) == 0) return NNTP_OK; if (strncmp(line, "436", 3) == 0) return NNTP_DEFER; CU(NNTP_ERR_DELIVERY); CUS: nntp->lastresp = strdup(line); return rc; } char *nntp_error(nntp_rc_t rc) { char *str; str = "NNTP: errorcode has no description"; if (rc == NNTP_OK ) str = "NNTP: no error"; else if (rc == NNTP_EOF ) str = "NNTP: end of file"; else if (rc == NNTP_DEFER ) str = "NNTP: transmission deferred"; else if (rc == NNTP_FAKE ) str = "NNTP: fake status not real"; else if (rc == NNTP_ERR_SYSTEM ) str = "NNTP: see errno"; else if (rc == NNTP_ERR_ARG ) str = "NNTP: invalid argument"; else if (rc == NNTP_ERR_OVERFLOW) str = "NNTP: buffer overflow"; else if (rc == NNTP_ERR_DELIVERY) str = "NNTP: cannot deliver message"; else if (rc == NNTP_ERR_INIT ) str = "NNTP: initialization failed"; else if (rc == NNTP_ERR_UNKNOWN ) str = "NNTP: unknown error"; return str; }