ossp-pkg/lmtp2nntp/nntp.c
/*
** 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 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 <ossp@ossp.org>.
**
** nntp.c: Network News Transfer Protocol (NNTP) client library
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/time.h>
#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 <message-id>
* < 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 <CR-LF>.<CR-LF>
* < 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 <messageid>
* < 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;
}