/* * nntp.c: NNTP library (implementation) * * Copyright (c) 2001 The OSSP Project (http://www.ossp.org/) * Copyright (c) 2001 Cable & Wireless Deutschland (http://www.cw.com/de/) * * This file is part of OSSP lmtp2nntp, an NNTP speaking local * mailer which forwards mails as Usenet news articles via NNTP. * It can be found at http://www.ossp.com/pkg/lmtp2nntp/. * * Permission to use, copy, modify, and distribute this software for * any purpose with or without fee is hereby granted, provided that * the above copyright notice and this permission notice appear in all * copies. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include "nntp.h" /* 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 { int rfd; int wfd; nntp_io_t io; nntp_readline_t rl; struct timeval tv; }; nntp_t *nntp_create(int rfd, int wfd, nntp_io_t *io) { nntp_t *nntp; if ((nntp = (nntp_t *)malloc(sizeof(nntp_t))) == NULL) return NULL; if (io == NULL) { nntp->io.select = select; nntp->io.read = read; nntp->io.write = write; } else { nntp->io.select = io->select ? io->select : select; nntp->io.read = io->read ? io->read : read; nntp->io.write = io->write ? io->write : write; } nntp->rfd = rfd; nntp->wfd = wfd; nntp->rl.rl_cnt = 0; nntp->rl.rl_bufptr = NULL; nntp->rl.rl_buf[0] = '\0'; nntp_timeout(nntp, 3); return nntp; } nntp_rc_t nntp_timeout(nntp_t *nntp, long timeout) { nntp->tv.tv_sec = timeout; nntp->tv.tv_usec = 0; return NNTP_OK; } 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) { fprintf(stderr, "DEBUG: nntp_readline returned ***%d***\n", rc); return rc; } } while (line[0] == '1'); // fprintf(stderr, "DEBUG: nntp_readline got ***%s***\n", line); if (strncmp(line, "200", 3) == 0) return NNTP_OK; return NNTP_ERR_INIT; } void nntp_destroy(nntp_t *nntp) { if (nntp != NULL) free(nntp); return; } 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; struct timeval tv; fd_set fds; int rc; if (nntp == NULL) return NNTP_ERR_ARG; for (n = 0; n < buflen-1;) { /* fetch one character (but read more) */ if (rl->rl_cnt <= 0) { FD_ZERO(&fds); FD_SET(nntp->rfd, &fds); tv.tv_sec = nntp->tv.tv_sec; tv.tv_usec = nntp->tv.tv_usec; rc = nntp->io.select(nntp->rfd + 1, &fds, NULL, NULL, &tv); if (rc == 0) return NNTP_TIMEOUT; else if (rc == -1) return NNTP_ERR_SYSTEM; do { rl->rl_cnt = nntp->io.read(nntp->rfd, 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] = '\0'; /* string termination */ if (n == (buflen-1)) return NNTP_ERR_OVERFLOW; fprintf(stderr, "DEBUG: nntp_readline >>>%s\n", buf); 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"); fprintf(stderr, "DEBUG: nntp_writeline >>>%s", tmp); if (nntp->io.write(nntp->wfd, 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]; /* check if this server already knows that artice * > STAT * < 223 yes, article already known * < 430 no, i don't know the article, yet */ *line = '\0'; 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) return NNTP_ERR_POSTPERM; //FIXME /* 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) */ *line = '\0'; strcat(line, "POST "); 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, "xxx", 3) == 0) return NNTP_OK; if (strncmp(line, "340", 3) != 0) return NNTP_ERR_POSTPERM; //FIXME fprintf(stderr, "DEBUG: before write ***%s***(%d)\n", msg->cpMsg, strlen(msg->cpMsg)); if ((rc = nntp->io.write(nntp->wfd, msg->cpMsg, strlen(msg->cpMsg))) < 0) return NNTP_ERR_SYSTEM; fprintf(stderr, "DEBUG: after write, written = %i\n", rc); {//DEBUG paragraph int i; char buf[NNTP_LINE_MAXLEN]; fprintf(stderr, "DEBUG: before writeline\n"); if ((rc = nntp_writeline(nntp, "\r\n.\r\nHELP\r\n")) != NNTP_OK); fprintf(stderr, "DEBUG: writeline returned %d\n", rc); fprintf(stderr, "DEBUG: before io.read(%d, ..., %d)\n", nntp->rfd, NNTP_LINE_MAXLEN); i = nntp->io.read(nntp->rfd, &buf, NNTP_LINE_MAXLEN); fprintf(stderr, "DEBUG: io.read = ***%s***, rc = %d\n", buf, i); for (i=0; i<10; i++) { if ((rc = nntp_readline(nntp, line, sizeof(line))) != NNTP_OK) ; //return rc; fprintf(stderr, "DEBUG: answer to post = ***%s***, rc = %s\n", line, nntp_error(nntp, rc)); }; } return NNTP_OK; #if 0 /* check if this server accepts at least one of the newsgroups > GROUP < 211 yes, group exists < 411 no, group doesn't exist */ #endif } char *nntp_error(nntp_t *nntp, 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_TIMEOUT ) str = "NNTP: timeout"; 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"; return str; }