#include <config.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/param.h>
#include <syslog.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <sasl/sasl.h>
#include <sasl/saslutil.h>
#include "acl.h"
#include "annotate.h"
#include "append.h"
#include "auth.h"
#include "backend.h"
#include "duplicate.h"
#include "exitcodes.h"
#include "global.h"
#include "hash.h"
#include "imap_err.h"
#include "index.h"
#include "iptostring.h"
#include "mailbox.h"
#include "map.h"
#include "mboxlist.h"
#include "mkgmtime.h"
#include "mupdate-client.h"
#include "nntp_err.h"
#include "prot.h"
#include "retry.h"
#include "rfc822date.h"
#include "smtpclient.h"
#include "spool.h"
#include "telemetry.h"
#include "tls.h"
#include "util.h"
#include "version.h"
#include "wildmat.h"
#include "xmalloc.h"
extern int optind;
extern char *optarg;
extern int opterr;
int imapd_exists;
struct protstream *imapd_out = NULL;
struct auth_state *imapd_authstate = NULL;
char *imapd_userid = NULL;
void printastring(const char *s __attribute__((unused)))
{
fatal("not implemented", EC_SOFTWARE);
}
#define IDLE_TIMEOUT (5 * 60)
struct backend *backend_current = NULL;
struct backend **backend_cached = NULL;
#ifdef HAVE_SSL
static SSL *tls_conn;
#endif
sasl_conn_t *nntp_saslconn;
char newsprefix[100] = "";
char *nntp_userid = 0, *newsmaster;
struct auth_state *nntp_authstate = 0, *newsmaster_authstate;
static struct mailbox *nntp_group = 0;
struct sockaddr_storage nntp_localaddr, nntp_remoteaddr;
int nntp_haveaddr = 0;
char nntp_clienthost[NI_MAXHOST*2+1] = "[local]";
struct protstream *nntp_out = NULL;
struct protstream *nntp_in = NULL;
static int nntp_logfd = -1;
unsigned nntp_exists = 0;
unsigned nntp_current = 0;
unsigned did_capabilities = 0;
int allowanonymous = 0;
int singleinstance = 1;
struct stagemsg *stage = NULL;
enum {
MODE_READ = (1<<0),
MODE_FEED = (1<<1)
};
static unsigned nntp_capa = MODE_READ | MODE_FEED;
static int nntps = 0;
int nntp_starttls_done = 0;
static struct mailbox mboxstruct;
static struct proxy_context nntp_proxyctx = {
0, 1, &nntp_authstate, NULL, NULL
};
const int config_need_data = CONFIG_NEED_PARTITION_DATA;
enum {
ARTICLE_ALL = 0,
ARTICLE_HEAD = 1,
ARTICLE_BODY = 2,
ARTICLE_STAT = 3
};
enum {
POST_POST = 0,
POST_IHAVE = 1,
POST_CHECK = 2,
POST_TAKETHIS = 3
};
struct {
int ok, cont, no, fail;
} post_codes[] = { { 240, 340, 440, 441 },
{ 235, 335, 435, 436 },
{ -1, 238, 438, -1 },
{ 239, -1, -1, 439 } };
struct wildmat {
char *pat;
int not;
};
static struct wildmat *split_wildmats(char *str);
static void free_wildmats(struct wildmat *wild);
static void cmdloop(void);
static int open_group(char *name, int has_prefix,
struct backend **ret, int *postable);
static int parserange(char *str, unsigned long *uid, unsigned long *last,
char **msgid, struct backend **be);
static time_t parse_datetime(char *datestr, char *timestr, char *gmt);
static void cmd_article(int part, char *msgid, unsigned long uid);
static void cmd_authinfo_user(char *user);
static void cmd_authinfo_pass(char *pass);
static void cmd_authinfo_sasl(char *cmd, char *mech, char *resp);
static void cmd_capabilities(char *keyword);
static void cmd_hdr(char *cmd, char *hdr, char *pat, char *msgid,
unsigned long uid, unsigned long last);
static void cmd_help(void);
static void cmd_list(char *arg1, char *arg2);
static void cmd_mode(char *arg);
static void cmd_newgroups(time_t tstamp);
static void cmd_newnews(char *wild, time_t tstamp);
static void cmd_over(char *msgid, unsigned long uid, unsigned long last);
static void cmd_post(char *msgid, int mode);
static void cmd_starttls(int nntps);
void usage(void);
void shut_down(int code) __attribute__ ((noreturn));
extern void setproctitle_init(int argc, char **argv, char **envp);
extern int proc_register(const char *progname, const char *clienthost,
const char *userid, const char *mailbox);
extern void proc_cleanup(void);
extern int saslserver(sasl_conn_t *conn, const char *mech,
const char *init_resp, const char *resp_prefix,
const char *continuation, const char *empty_resp,
struct protstream *pin, struct protstream *pout,
int *sasl_result, char **success_data);
static struct
{
char *ipremoteport;
char *iplocalport;
sasl_ssf_t ssf;
char *authid;
} saslprops = {NULL,NULL,0,NULL};
static struct sasl_callback mysasl_cb[] = {
{ SASL_CB_GETOPT, &mysasl_config, NULL },
{ SASL_CB_PROXY_POLICY, &mysasl_proxy_policy, (void*) &nntp_proxyctx },
{ SASL_CB_CANON_USER, &mysasl_canon_user, NULL },
{ SASL_CB_LIST_END, NULL, NULL }
};
void proxyd_downserver(struct backend *s)
{
if (!s || !s->timeout) {
return;
}
backend_disconnect(s, &protocol[PROTOCOL_NNTP]);
if(s == backend_current) backend_current = NULL;
prot_removewaitevent(nntp_in, s->timeout);
s->timeout = NULL;
}
struct prot_waitevent *backend_timeout(struct protstream *s __attribute__((unused)),
struct prot_waitevent *ev, void *rock)
{
struct backend *be = (struct backend *) rock;
if (be != backend_current) {
proxyd_downserver(be);
return NULL;
}
else {
ev->mark = time(NULL) + IDLE_TIMEOUT;
return ev;
}
}
struct backend *proxyd_findserver(const char *server)
{
int i = 0;
struct backend *ret = NULL;
while (backend_cached && backend_cached[i]) {
if (!strcmp(server, backend_cached[i]->hostname)) {
ret = backend_cached[i];
break;
}
i++;
}
if (!ret || !ret->timeout) {
ret = backend_connect(ret, server, &protocol[PROTOCOL_NNTP],
nntp_userid ? nntp_userid : "anonymous", NULL);
if(!ret) return NULL;
if (!ret->context) {
ret->context = xmalloc(sizeof(unsigned));
*((unsigned *) ret->context) = i;
}
ret->timeout = prot_addwaitevent(nntp_in, time(NULL) + IDLE_TIMEOUT,
backend_timeout, ret);
}
ret->timeout->mark = time(NULL) + IDLE_TIMEOUT;
if (!backend_cached[i]) {
backend_cached = (struct backend **)
xrealloc(backend_cached, (i + 2) * sizeof(struct backend *));
backend_cached[i] = ret;
backend_cached[i + 1] = NULL;
}
return ret;
}
static int mlookup(const char *name, char **server, char **aclp, void *tid)
{
int r, type;
if(server) *server = NULL;
r = mboxlist_detail(name, &type, NULL, server, aclp, tid);
if (r == IMAP_MAILBOX_NONEXISTENT && config_mupdate_server) {
kick_mupdate();
r = mboxlist_detail(name, &type, NULL, server, aclp, tid);
}
if (type & MBTYPE_REMOTE) {
if(server && *server) {
char *c;
c = strchr(*server, '!');
if(c) *c = '\0';
}
}
else if (server)
*server = NULL;
return r;
}
static int read_response(struct backend *s, int force_notfatal, char **result)
{
static char buf[2048];
s->timeout->mark = time(NULL) + IDLE_TIMEOUT;
if (!prot_fgets(buf, sizeof(buf), s->in)) {
if (s == backend_current && !force_notfatal)
fatal("Lost connection to selected backend", EC_UNAVAILABLE);
proxyd_downserver(s);
return IMAP_SERVER_UNAVAILABLE;
}
*result = buf;
return 0;
}
static int pipe_to_end_of_response(struct backend *s, int force_notfatal)
{
char buf[2048];
s->timeout->mark = time(NULL) + IDLE_TIMEOUT;
do {
if (!prot_fgets(buf, sizeof(buf), s->in)) {
if (s == backend_current && !force_notfatal)
fatal("Lost connection to selected backend", EC_UNAVAILABLE);
proxyd_downserver(s);
return IMAP_SERVER_UNAVAILABLE;
}
prot_printf(nntp_out, "%s", buf);
} while (strcmp(buf, ".\r\n"));
return 0;
}
static void nntp_reset(void)
{
int i;
proc_cleanup();
if (nntp_group) {
mailbox_close(nntp_group);
nntp_group = 0;
}
i = 0;
while (backend_cached && backend_cached[i]) {
proxyd_downserver(backend_cached[i]);
free(backend_cached[i]->context);
free(backend_cached[i]);
i++;
}
if (backend_cached) free(backend_cached);
backend_cached = NULL;
backend_current = NULL;
if (nntp_in) {
prot_NONBLOCK(nntp_in);
prot_fill(nntp_in);
prot_free(nntp_in);
}
if (nntp_out) {
prot_flush(nntp_out);
prot_free(nntp_out);
}
nntp_in = nntp_out = NULL;
#ifdef HAVE_SSL
if (tls_conn) {
tls_reset_servertls(&tls_conn);
tls_conn = NULL;
}
#endif
cyrus_reset_stdio();
strcpy(nntp_clienthost, "[local]");
if (nntp_logfd != -1) {
close(nntp_logfd);
nntp_logfd = -1;
}
if (nntp_userid != NULL) {
free(nntp_userid);
nntp_userid = NULL;
}
if (nntp_authstate) {
auth_freestate(nntp_authstate);
nntp_authstate = NULL;
}
if (nntp_saslconn) {
sasl_dispose(&nntp_saslconn);
nntp_saslconn = NULL;
}
nntp_starttls_done = 0;
if(saslprops.iplocalport) {
free(saslprops.iplocalport);
saslprops.iplocalport = NULL;
}
if(saslprops.ipremoteport) {
free(saslprops.ipremoteport);
saslprops.ipremoteport = NULL;
}
if(saslprops.authid) {
free(saslprops.authid);
saslprops.authid = NULL;
}
saslprops.ssf = 0;
nntp_exists = 0;
nntp_current = 0;
did_capabilities = 0;
}
int service_init(int argc __attribute__((unused)),
char **argv __attribute__((unused)),
char **envp __attribute__((unused)))
{
int opt;
const char *prefix;
initialize_nntp_error_table();
if (geteuid() == 0) fatal("must run as the Cyrus user", EC_USAGE);
setproctitle_init(argc, argv, envp);
signals_set_shutdown(&shut_down);
signal(SIGPIPE, SIG_IGN);
global_sasl_init(1, 1, mysasl_cb);
if ((prefix = config_getstring(IMAPOPT_NEWSPREFIX)))
snprintf(newsprefix, sizeof(newsprefix), "%s.", prefix);
if (duplicate_init(NULL, 0) != 0) {
syslog(LOG_ERR,
"unable to init duplicate delivery database\n");
fatal("unable to init duplicate delivery database", EC_SOFTWARE);
}
mboxlist_init(0);
mboxlist_open(NULL);
quotadb_init(0);
quotadb_open(NULL);
while ((opt = getopt(argc, argv, "srf")) != EOF) {
switch(opt) {
case 's':
nntps = 1;
if (!tls_enabled()) {
syslog(LOG_ERR, "nntps: required OpenSSL options not present");
fatal("nntps: required OpenSSL options not present",
EC_CONFIG);
}
break;
case 'r':
nntp_capa = MODE_READ;
break;
case 'f':
nntp_capa = MODE_FEED;
break;
default:
usage();
}
}
annotatemore_init(0, NULL, NULL);
annotatemore_open(NULL);
newsmaster = (char *) config_getstring(IMAPOPT_NEWSMASTER);
newsmaster_authstate = auth_newstate(newsmaster);
singleinstance = config_getswitch(IMAPOPT_SINGLEINSTANCESTORE);
return 0;
}
int service_main(int argc __attribute__((unused)),
char **argv __attribute__((unused)),
char **envp __attribute__((unused)))
{
socklen_t salen;
char localip[60], remoteip[60];
char hbuf[NI_MAXHOST];
int niflags;
int timeout;
sasl_security_properties_t *secprops=NULL;
char unavail[1024];
signals_poll();
nntp_in = prot_new(0, 0);
nntp_out = prot_new(1, 1);
salen = sizeof(nntp_remoteaddr);
if (getpeername(0, (struct sockaddr *)&nntp_remoteaddr, &salen) == 0 &&
(nntp_remoteaddr.ss_family == AF_INET ||
nntp_remoteaddr.ss_family == AF_INET6)) {
if (getnameinfo((struct sockaddr *)&nntp_remoteaddr, salen,
hbuf, sizeof(hbuf), NULL, 0, NI_NAMEREQD) == 0) {
strncpy(nntp_clienthost, hbuf, sizeof(hbuf));
strlcat(nntp_clienthost, " ", sizeof(nntp_clienthost));
nntp_clienthost[sizeof(nntp_clienthost)-30] = '\0';
} else {
nntp_clienthost[0] = '\0';
}
niflags = NI_NUMERICHOST;
#ifdef NI_WITHSCOPEID
if (((struct sockaddr *)&nntp_remoteaddr)->sa_family == AF_INET6)
niflags |= NI_WITHSCOPEID;
#endif
if (getnameinfo((struct sockaddr *)&nntp_remoteaddr, salen, hbuf,
sizeof(hbuf), NULL, 0, niflags) != 0)
strlcpy(hbuf, "unknown", sizeof(hbuf));
strlcat(nntp_clienthost, "[", sizeof(nntp_clienthost));
strlcat(nntp_clienthost, hbuf, sizeof(nntp_clienthost));
strlcat(nntp_clienthost, "]", sizeof(nntp_clienthost));
salen = sizeof(nntp_localaddr);
if (getsockname(0, (struct sockaddr *)&nntp_localaddr, &salen) == 0) {
nntp_haveaddr = 1;
}
}
if (sasl_server_new("nntp", config_servername, NULL, NULL, NULL,
NULL, SASL_SUCCESS_DATA, &nntp_saslconn) != SASL_OK)
fatal("SASL failed initializing: sasl_server_new()",EC_TEMPFAIL);
secprops = mysasl_secprops(SASL_SEC_NOPLAINTEXT);
sasl_setprop(nntp_saslconn, SASL_SEC_PROPS, secprops);
if(iptostring((struct sockaddr *)&nntp_localaddr, salen,
localip, 60) == 0) {
sasl_setprop(nntp_saslconn, SASL_IPLOCALPORT, localip);
saslprops.iplocalport = xstrdup(localip);
}
if(iptostring((struct sockaddr *)&nntp_remoteaddr, salen,
remoteip, 60) == 0) {
sasl_setprop(nntp_saslconn, SASL_IPREMOTEPORT, remoteip);
saslprops.ipremoteport = xstrdup(remoteip);
}
proc_register("nntpd", nntp_clienthost, NULL, NULL);
timeout = config_getint(IMAPOPT_TIMEOUT);
if (timeout < 3) timeout = 3;
prot_settimeout(nntp_in, timeout*60);
prot_setflushonread(nntp_in, nntp_out);
if (config_mupdate_server) {
backend_cached = xmalloc(sizeof(struct backend *));
backend_cached[0] = NULL;
}
if (nntps == 1) cmd_starttls(1);
if (shutdown_file(unavail, sizeof(unavail))) {
prot_printf(nntp_out,
"400 %s Cyrus NNTP%s %s server unavailable, %s\r\n",
config_servername, config_mupdate_server ? " Murder" : "",
CYRUS_VERSION, unavail);
shut_down(0);
}
prot_printf(nntp_out,
"%u %s Cyrus NNTP%s %s server ready, posting %s\r\n",
(nntp_capa & MODE_READ) ? 200 : 201,
config_servername, config_mupdate_server ? " Murder" : "",
CYRUS_VERSION,
(nntp_capa & MODE_READ) ? "allowed" : "prohibited");
cmdloop();
nntp_reset();
return 0;
}
void service_abort(int error)
{
shut_down(error);
}
void usage(void)
{
prot_printf(nntp_out, "503 usage: nntpd [-C <alt_config>] [-s]\r\n");
prot_flush(nntp_out);
exit(EC_USAGE);
}
void shut_down(int code)
{
int i;
proc_cleanup();
if (nntp_group) {
mailbox_close(nntp_group);
}
i = 0;
while (backend_cached && backend_cached[i]) {
proxyd_downserver(backend_cached[i]);
free(backend_cached[i]->context);
free(backend_cached[i]);
i++;
}
if (backend_cached) free(backend_cached);
duplicate_done();
mboxlist_close();
mboxlist_done();
quotadb_close();
quotadb_done();
annotatemore_close();
annotatemore_done();
if (nntp_in) {
prot_NONBLOCK(nntp_in);
prot_fill(nntp_in);
prot_free(nntp_in);
}
if (nntp_out) {
prot_flush(nntp_out);
prot_free(nntp_out);
}
#ifdef HAVE_SSL
tls_shutdown_serverengine();
#endif
cyrus_done();
exit(code);
}
void fatal(const char* s, int code)
{
static int recurse_code = 0;
if (recurse_code) {
proc_cleanup();
exit(recurse_code);
}
recurse_code = code;
if (nntp_out) {
prot_printf(nntp_out, "205 Fatal error: %s\r\n", s);
prot_flush(nntp_out);
}
if (stage) append_removestage(stage);
syslog(LOG_ERR, "Fatal error: %s", s);
shut_down(code);
}
static int reset_saslconn(sasl_conn_t **conn)
{
int ret;
sasl_security_properties_t *secprops = NULL;
sasl_dispose(conn);
ret = sasl_server_new("news", config_servername,
NULL, NULL, NULL,
NULL, SASL_SUCCESS_DATA, conn);
if(ret != SASL_OK) return ret;
if(saslprops.ipremoteport)
ret = sasl_setprop(*conn, SASL_IPREMOTEPORT,
saslprops.ipremoteport);
if(ret != SASL_OK) return ret;
if(saslprops.iplocalport)
ret = sasl_setprop(*conn, SASL_IPLOCALPORT,
saslprops.iplocalport);
if(ret != SASL_OK) return ret;
secprops = mysasl_secprops(SASL_SEC_NOPLAINTEXT);
ret = sasl_setprop(*conn, SASL_SEC_PROPS, secprops);
if(ret != SASL_OK) return ret;
if(saslprops.ssf) {
ret = sasl_setprop(*conn, SASL_SSF_EXTERNAL, &saslprops.ssf);
}
if(ret != SASL_OK) return ret;
if(saslprops.authid) {
ret = sasl_setprop(*conn, SASL_AUTH_EXTERNAL, saslprops.authid);
if(ret != SASL_OK) return ret;
}
return SASL_OK;
}
static void cmdloop(void)
{
int c, r = 0, mode;
static struct buf cmd, arg1, arg2, arg3, arg4;
char *p, *result, buf[1024];
const char *err;
unsigned long uid;
struct backend *be;
allowanonymous = config_getswitch(IMAPOPT_ALLOWANONYMOUSLOGIN);
for (;;) {
signals_poll();
c = getword(nntp_in, &cmd);
if (c == EOF) {
if ((err = prot_error(nntp_in)) != NULL
&& strcmp(err, PROT_EOF_STRING)) {
syslog(LOG_WARNING, "%s, closing connection", err);
prot_printf(nntp_out, "400 %s\r\n", err);
}
return;
}
if (shutdown_file(buf, sizeof(buf))) {
prot_printf(nntp_out, "400 %s\r\n", buf);
shut_down(0);
}
if (!cmd.s[0]) {
prot_printf(nntp_out, "501 Empty command\r\n");
eatline(nntp_in, c);
continue;
}
if (islower((unsigned char) cmd.s[0]))
cmd.s[0] = toupper((unsigned char) cmd.s[0]);
for (p = &cmd.s[1]; *p; p++) {
if (isupper((unsigned char) *p)) *p = tolower((unsigned char) *p);
}
if (!(nntp_capa & MODE_FEED) &&
strchr("IT", cmd.s[0])) goto noperm;
if (!(nntp_capa & MODE_READ) &&
strchr("BDGNOPX", cmd.s[0])) goto noperm;
if (!nntp_userid && !allowanonymous &&
!strchr("ACHILMQST", cmd.s[0])) goto nologin;
switch (cmd.s[0]) {
case 'A':
if (!strcmp(cmd.s, "Authinfo")) {
arg2.len = arg3.len = 0;
if (c != ' ') goto missingargs;
c = getword(nntp_in, &arg1);
if (c == EOF) goto missingargs;
lcase(arg1.s);
if (strcmp(arg1.s, "generic") && c != ' ') {
goto missingargs;
}
if (c == ' ') {
c = getword(nntp_in, &arg2);
if (c == EOF) goto missingargs;
}
if (!strcmp(arg1.s, "sasl") && c == ' ') {
c = getword(nntp_in, &arg3);
if (c == EOF) goto missingargs;
}
if (c == '\r') c = prot_getc(nntp_in);
if (c != '\n') goto extraargs;
if (!strcmp(arg1.s, "user"))
cmd_authinfo_user(arg2.s);
else if (!strcmp(arg1.s, "pass"))
cmd_authinfo_pass(arg2.s);
else if (!strcmp(arg1.s, "sasl") || !strcmp(arg1.s, "generic"))
cmd_authinfo_sasl(arg1.s, arg2.len ? arg2.s : NULL,
arg3.len ? arg3.s : NULL);
else
prot_printf(nntp_out,
"501 Unrecognized AUTHINFO command\r\n");
}
else if (!(nntp_capa & MODE_READ)) goto noperm;
else if (!nntp_userid && !allowanonymous) goto nologin;
else if (!strcmp(cmd.s, "Article")) {
char curgroup[MAX_MAILBOX_NAME+1], *msgid;
mode = ARTICLE_ALL;
article:
if (arg1.s) *arg1.s = 0;
if (c == ' ') {
c = getword(nntp_in, &arg1);
if (c == EOF) goto missingargs;
}
if (c == '\r') c = prot_getc(nntp_in);
if (c != '\n') goto extraargs;
strcpy(curgroup, nntp_group ? nntp_group->name : "");
if (parserange(arg1.s, &uid, NULL, &msgid, &be) != -1) {
if (be) {
if (arg1.s && *arg1.s)
prot_printf(be->out, "%s %s\r\n", cmd.s, arg1.s);
else
prot_printf(be->out, "%s\r\n", cmd.s);
r = read_response(be, 0, &result);
if (r) goto noopengroup;
prot_printf(nntp_out, "%s", result);
if (!strncmp(result, "22", 2) &&
mode != ARTICLE_STAT) {
pipe_to_end_of_response(be, 0);
}
}
else
cmd_article(mode, msgid, uid);
}
if (*curgroup && nntp_group &&
strcmp(curgroup, nntp_group->name)) {
open_group(curgroup, 1, NULL, NULL);
}
}
else goto badcmd;
break;
case 'B':
if (!strcmp(cmd.s, "Body")) {
mode = ARTICLE_BODY;
goto article;
}
else goto badcmd;
break;
case 'C':
if (!strcmp(cmd.s, "Capabilities")) {
arg1.len = 0;
if (c == ' ') {
c = getword(nntp_in, &arg1);
if (c == EOF) goto missingargs;
}
if (c == '\r') c = prot_getc(nntp_in);
if (c != '\n') goto extraargs;
cmd_capabilities(arg1.s);
}
else if (!(nntp_capa & MODE_FEED)) goto noperm;
else if (!strcmp(cmd.s, "Check")) {
mode = POST_CHECK;
goto ihave;
}
else goto badcmd;
break;
case 'D':
if (!strcmp(cmd.s, "Date")) {
time_t now = time(NULL);
struct tm *my_tm = gmtime(&now);
char buf[15];
if (c == '\r') c = prot_getc(nntp_in);
if (c != '\n') goto extraargs;
strftime(buf, sizeof(buf), "%Y%m%d%H%M%S", my_tm);
prot_printf(nntp_out, "111 %s\r\n", buf);
}
else goto badcmd;
break;
case 'G':
if (!strcmp(cmd.s, "Group")) {
if (c != ' ') goto missingargs;
c = getword(nntp_in, &arg1);
if (c == EOF) goto missingargs;
if (c == '\r') c = prot_getc(nntp_in);
if (c != '\n') goto extraargs;
r = open_group(arg1.s, 0, &backend_current, NULL);
if (r) goto nogroup;
else if (backend_current) {
prot_printf(backend_current->out, "GROUP %s\r\n", arg1.s);
r = read_response(backend_current, 0, &result);
if (r) goto nogroup;
prot_printf(nntp_out, "%s", result);
}
else {
nntp_exists = nntp_group->exists;
nntp_current = nntp_exists > 0;
prot_printf(nntp_out, "211 %u %lu %lu %s\r\n",
nntp_exists,
nntp_exists ? index_getuid(1) :
nntp_group->last_uid+1,
nntp_exists ? index_getuid(nntp_exists) :
nntp_group->last_uid,
arg1.s);
}
}
else goto badcmd;
break;
case 'H':
if (!strcmp(cmd.s, "Head")) {
mode = ARTICLE_HEAD;
goto article;
}
else if (!strcmp(cmd.s, "Help")) {
if (c == '\r') c = prot_getc(nntp_in);
if (c != '\n') goto extraargs;
cmd_help();
}
else if (!(nntp_capa & MODE_READ)) goto noperm;
else if (!nntp_userid && !allowanonymous) goto nologin;
else if (!strcmp(cmd.s, "Hdr")) {
char curgroup[MAX_MAILBOX_NAME+1], *msgid;
unsigned long last;
hdr:
if (arg2.s) *arg2.s = 0;
if (c != ' ') goto missingargs;
c = getword(nntp_in, &arg1);
if (c == EOF) goto missingargs;
if (c == ' ') {
c = getword(nntp_in, &arg2);
if (c == EOF) goto missingargs;
}
if (c == '\r') c = prot_getc(nntp_in);
if (c != '\n') goto extraargs;
strcpy(curgroup, nntp_group ? nntp_group->name : "");
if (parserange(arg2.s, &uid, &last, &msgid, &be) != -1) {
if (be) {
if (arg2.s && *arg2.s)
prot_printf(be->out, "%s %s %s\r\n",
cmd.s, arg1.s, arg2.s);
else
prot_printf(be->out, "%s %s\r\n", cmd.s, arg1.s);
r = read_response(be, 0, &result);
if (r) goto noopengroup;
prot_printf(nntp_out, "%s", result);
if (!strncmp(result, "22", 2)) {
pipe_to_end_of_response(be, 0);
}
}
else
cmd_hdr(cmd.s, arg1.s, NULL, msgid, uid, last);
}
if (*curgroup && nntp_group &&
strcmp(curgroup, nntp_group->name)) {
open_group(curgroup, 1, NULL, NULL);
}
}
else goto badcmd;
break;
case 'I':
if (!strcmp(cmd.s, "Ihave")) {
mode = POST_IHAVE;
ihave:
if (c != ' ') goto missingargs;
c = getword(nntp_in, &arg1);
if (c == EOF) goto missingargs;
if (c == '\r') c = prot_getc(nntp_in);
if (c != '\n') goto extraargs;
cmd_post(arg1.s, mode);
}
else goto badcmd;
break;
case 'L':
if (!strcmp(cmd.s, "List")) {
arg1.len = arg2.len = 0;
if (c == ' ') {
c = getword(nntp_in, &arg1);
if (c == EOF) goto missingargs;
if (c == ' ') {
c = getword(nntp_in, &arg2);
if (c == EOF) goto missingargs;
}
}
if (c == '\r') c = prot_getc(nntp_in);
if (c != '\n') goto extraargs;
cmd_list(arg1.len ? arg1.s : NULL, arg2.len ? arg2.s : NULL);
}
else if (!(nntp_capa & MODE_READ)) goto noperm;
else if (!nntp_userid && !allowanonymous) goto nologin;
else if (!strcmp(cmd.s, "Last")) {
if (c == '\r') c = prot_getc(nntp_in);
if (c != '\n') goto extraargs;
if (backend_current) {
prot_printf(backend_current->out, "LAST\r\n");
r = read_response(backend_current, 0, &result);
if (r) goto noopengroup;
prot_printf(nntp_out, "%s", result);
}
else if (!nntp_group) goto noopengroup;
else if (!nntp_current) goto nocurrent;
else if (nntp_current == 1) {
prot_printf(nntp_out,
"422 No previous article in this group\r\n");
}
else {
char *msgid = index_get_msgid(nntp_group, --nntp_current);
prot_printf(nntp_out, "223 %u %s\r\n",
index_getuid(nntp_current),
msgid ? msgid : "<0>");
if (msgid) free(msgid);
}
}
else if (!strcmp(cmd.s, "Listgroup")) {
arg1.len = 0;
if (c == ' ') {
c = getword(nntp_in, &arg1);
if (c == EOF) goto missingargs;
}
if (c == '\r') c = prot_getc(nntp_in);
if (c != '\n') goto extraargs;
if (arg1.len) {
r = open_group(arg1.s, 0, &backend_current, NULL);
if (r) goto nogroup;
if (nntp_group) {
nntp_exists = nntp_group->exists;
nntp_current = nntp_exists > 0;
}
}
if (backend_current) {
if (arg1.len)
prot_printf(backend_current->out, "LISTGROUP %s\r\n",
arg1.s);
else
prot_printf(backend_current->out, "LISTGROUP\r\n");
r = read_response(backend_current, 0, &result);
if (r) goto noopengroup;
prot_printf(nntp_out, "%s", result);
if (!strncmp(result, "211", 3)) {
pipe_to_end_of_response(backend_current, 0);
}
}
else if (!nntp_group) goto noopengroup;
else {
int i;
nntp_exists = nntp_group->exists;
nntp_current = nntp_exists > 0;
prot_printf(nntp_out, "211 %u %lu %lu %s\r\n",
nntp_exists,
nntp_exists ? index_getuid(1) :
nntp_group->last_uid+1,
nntp_exists ? index_getuid(nntp_exists) :
nntp_group->last_uid,
nntp_group->name + strlen(newsprefix));
for (i = 1; i <= nntp_exists; i++)
prot_printf(nntp_out, "%u\r\n", index_getuid(i));
prot_printf(nntp_out, ".\r\n");
}
}
else goto badcmd;
break;
case 'M':
if (!strcmp(cmd.s, "Mode")) {
if (c != ' ') goto missingargs;
c = getword(nntp_in, &arg1);
if (c == EOF) goto missingargs;
if (c == '\r') c = prot_getc(nntp_in);
if (c != '\n') goto extraargs;
cmd_mode(arg1.s);
}
else goto badcmd;
break;
case 'N':
if (!strcmp(cmd.s, "Newgroups")) {
time_t tstamp;
arg3.len = 0;
if (c != ' ') goto missingargs;
c = getword(nntp_in, &arg1);
if (c != ' ') goto missingargs;
c = getword(nntp_in, &arg2);
if (c == EOF) goto missingargs;
if (c == ' ') {
c = getword(nntp_in, &arg3);
if (c == EOF) goto missingargs;
}
if (c == '\r') c = prot_getc(nntp_in);
if (c != '\n') goto extraargs;
if ((tstamp = parse_datetime(arg1.s, arg2.s,
arg3.len ? arg3.s : NULL)) < 0)
goto baddatetime;
cmd_newgroups(tstamp);
}
else if (!strcmp(cmd.s, "Newnews")) {
time_t tstamp;
if (!config_getswitch(IMAPOPT_ALLOWNEWNEWS))
goto cmddisabled;
arg4.len = 0;
if (c != ' ') goto missingargs;
c = getword(nntp_in, &arg1);
if (c != ' ') goto missingargs;
c = getword(nntp_in, &arg2);
if (c != ' ') goto missingargs;
c = getword(nntp_in, &arg3);
if (c == EOF) goto missingargs;
if (c == ' ') {
c = getword(nntp_in, &arg4);
if (c == EOF) goto missingargs;
}
if (c == '\r') c = prot_getc(nntp_in);
if (c != '\n') goto extraargs;
if ((tstamp = parse_datetime(arg2.s, arg3.s,
arg4.len ? arg4.s : NULL)) < 0)
goto baddatetime;
cmd_newnews(arg1.s, tstamp);
}
else if (!strcmp(cmd.s, "Next")) {
if (c == '\r') c = prot_getc(nntp_in);
if (c != '\n') goto extraargs;
if (backend_current) {
prot_printf(backend_current->out, "NEXT\r\n");
r = read_response(backend_current, 0, &result);
if (r) goto noopengroup;
prot_printf(nntp_out, "%s", result);
}
else if (!nntp_group) goto noopengroup;
else if (!nntp_current) goto nocurrent;
else if (nntp_current == nntp_exists) {
prot_printf(nntp_out,
"421 No next article in this group\r\n");
}
else {
char *msgid = index_get_msgid(nntp_group, ++nntp_current);
prot_printf(nntp_out, "223 %u %s\r\n",
index_getuid(nntp_current),
msgid ? msgid : "<0>");
if (msgid) free(msgid);
}
}
else goto badcmd;
break;
case 'O':
if (!strcmp(cmd.s, "Over")) {
char curgroup[MAX_MAILBOX_NAME+1], *msgid;
unsigned long last;
over:
if (arg1.s) *arg1.s = 0;
if (c == ' ') {
c = getword(nntp_in, &arg1);
if (c == EOF) goto missingargs;
}
if (c == '\r') c = prot_getc(nntp_in);
if (c != '\n') goto extraargs;
strcpy(curgroup, nntp_group ? nntp_group->name : "");
msgid = NULL;
if (parserange(arg1.s, &uid, &last,
(cmd.s[0] == 'X' ? NULL : &msgid), &be) != -1) {
if (be) {
if (arg1.s && *arg1.s)
prot_printf(be->out, "%s %s\r\n", cmd.s, arg1.s);
else
prot_printf(be->out, "%s\r\n", cmd.s);
r = read_response(be, 0, &result);
if (r) goto noopengroup;
prot_printf(nntp_out, "%s", result);
if (!strncmp(result, "224", 3)) {
pipe_to_end_of_response(be, 0);
}
}
else
cmd_over(msgid, uid, last);
}
if (*curgroup && nntp_group &&
strcmp(curgroup, nntp_group->name)) {
open_group(curgroup, 1, NULL, NULL);
}
}
else goto badcmd;
break;
case 'P':
if (!strcmp(cmd.s, "Post")) {
if (c == '\r') c = prot_getc(nntp_in);
if (c != '\n') goto extraargs;
cmd_post(NULL, POST_POST);
}
else goto badcmd;
break;
case 'Q':
if (!strcmp(cmd.s, "Quit")) {
if (c == '\r') c = prot_getc(nntp_in);
if (c != '\n') goto extraargs;
prot_printf(nntp_out, "205 Connection closing\r\n");
return;
}
else goto badcmd;
break;
case 'S':
if (!strcmp(cmd.s, "Starttls") && tls_enabled()) {
if (c == '\r') c = prot_getc(nntp_in);
if (c != '\n') goto extraargs;
cmd_starttls(0);
}
else if (!strcmp(cmd.s, "Stat")) {
mode = ARTICLE_STAT;
goto article;
}
else if (!nntp_userid && !allowanonymous) goto nologin;
else if (!strcmp(cmd.s, "Slave")) {
if (c == '\r') c = prot_getc(nntp_in);
if (c != '\n') goto extraargs;
prot_printf(nntp_out, "202 Slave status noted\r\n");
}
else goto badcmd;
break;
case 'T':
if (!strcmp(cmd.s, "Takethis")) {
mode = POST_TAKETHIS;
goto ihave;
}
else goto badcmd;
break;
case 'X':
if (!strcmp(cmd.s, "Xhdr")) {
goto hdr;
}
else if (!strcmp(cmd.s, "Xover")) {
goto over;
}
else if (!strcmp(cmd.s, "Xpat")) {
char curgroup[MAX_MAILBOX_NAME+1], *msgid;
unsigned long last;
if (c != ' ') goto missingargs;
c = getword(nntp_in, &arg1);
if (c != ' ') goto missingargs;
while ((c = prot_getc(nntp_in)) == ' ');
prot_ungetc(c, nntp_in);
c = getword(nntp_in, &arg2);
if (c != ' ') goto missingargs;
c = getword(nntp_in, &arg3);
if (c == EOF) goto missingargs;
if (c == '\r') c = prot_getc(nntp_in);
if (c != '\n') goto extraargs;
strcpy(curgroup, nntp_group ? nntp_group->name : "");
if (parserange(arg2.s, &uid, &last, &msgid, &be) != -1) {
if (be) {
prot_printf(be->out, "%s %s %s %s\r\n",
cmd.s, arg1.s, arg2.s, arg3.s);
r = read_response(be, 0, &result);
if (r) goto noopengroup;
prot_printf(nntp_out, "%s", result);
if (!strncmp(result, "221", 3)) {
pipe_to_end_of_response(be, 0);
}
}
else
cmd_hdr(cmd.s, arg1.s, arg3.s, msgid, uid, last);
}
if (*curgroup && nntp_group &&
strcmp(curgroup, nntp_group->name)) {
open_group(curgroup, 1, NULL, NULL);
}
}
else goto badcmd;
break;
default:
badcmd:
prot_printf(nntp_out, "500 Unrecognized command\r\n");
eatline(nntp_in, c);
}
continue;
noperm:
prot_printf(nntp_out, "502 Permission denied\r\n");
eatline(nntp_in, c);
continue;
nologin:
prot_printf(nntp_out, "480 Authentication required\r\n");
eatline(nntp_in, c);
continue;
cmddisabled:
prot_printf(nntp_out, "503 \"%s\" disabled\r\n", cmd.s);
eatline(nntp_in, c);
continue;
extraargs:
prot_printf(nntp_out, "501 Unexpected extra argument\r\n");
eatline(nntp_in, c);
continue;
missingargs:
prot_printf(nntp_out, "501 Missing argument\r\n");
eatline(nntp_in, c);
continue;
baddatetime:
prot_printf(nntp_out, "501 Bad date/time\r\n");
continue;
nogroup:
prot_printf(nntp_out, "411 No such newsgroup (%s)\r\n",
error_message(r));
continue;
noopengroup:
prot_printf(nntp_out, "412 No newsgroup selected\r\n");
continue;
nocurrent:
prot_printf(nntp_out, "420 Current article number is invalid\r\n");
continue;
}
}
struct findrock {
const char *mailbox;
unsigned long uid;
};
static int find_cb(const char *msgid __attribute__((unused)),
const char *mailbox,
time_t mark __attribute__((unused)),
unsigned long uid, void *rock)
{
struct findrock *frock = (struct findrock *) rock;
if (!strncmp(mailbox, "user.", 5) ||
strncmp(mailbox, newsprefix, strlen(newsprefix))) return 0;
frock->mailbox = mailbox;
frock->uid = uid;
return CYRUSDB_DONE;
}
static int find_msgid(char *msgid, char **mailbox, unsigned long *uid)
{
struct findrock frock = { NULL, 0 };
duplicate_find(msgid, &find_cb, &frock);
if (!frock.mailbox) return 0;
if (mailbox) {
if (!frock.mailbox[0]) return 0;
*mailbox = (char *) frock.mailbox;
}
if (uid) {
if (!frock.uid) return 0;
*uid = frock.uid;
}
return 1;
}
static int parsenum(char *str, char **rem)
{
char *p = str;
int result = 0;
while (*p && isdigit((int) *p)) {
result = result * 10 + *p++ - '0';
if (result < 0) {
}
}
if (rem) {
*rem = p;
return (*p && p == str ? -1 : result);
}
return (*p ? -1 : result);
}
static int parserange(char *str, unsigned long *uid, unsigned long *last,
char **msgid, struct backend **ret)
{
char *p = NULL, *mboxname;
int r = 0;
*uid = 0;
if (last) *last = 0;
if (msgid) *msgid = NULL;
if (ret) *ret = NULL;
if (!str || !*str) {
if (backend_current) {
if (ret) *ret = backend_current;
}
else if (!nntp_group) goto noopengroup;
else if (!nntp_current) goto nocurrent;
else {
*uid = index_getuid(nntp_current);
if (last) *last = *uid;
}
}
else if (*str == '<') {
if (!msgid) goto badrange;
if (!find_msgid(str, &mboxname, uid)) goto nomsgid;
if (!nntp_group || strcmp(mboxname, nntp_group->name)) {
if ((r = open_group(mboxname, 1, ret, NULL))) goto nomsgid;
*msgid = str;
}
}
else if (backend_current)
*ret = backend_current;
else if (!nntp_group) goto noopengroup;
else if (!nntp_exists) goto noarticle;
else if ((*uid = parsenum(str, &p)) <= 0) goto badrange;
else if (p && *p) {
if (!last || (*p != '-')) goto badrange;
if (*++p)
*last = parsenum(p, NULL);
else
*last = index_getuid(nntp_exists);
if (*last <= 0 || *last < *uid) goto badrange;
}
if (last && !*last) *last = *uid;
return 0;
noopengroup:
prot_printf(nntp_out, "412 No newsgroup selected\r\n");
return -1;
nocurrent:
prot_printf(nntp_out, "420 Current article number is invalid\r\n");
return -1;
noarticle:
prot_printf(nntp_out, "423 No such article in this newsgroup\r\n");
return -1;
nomsgid:
prot_printf(nntp_out, "430 No article found with that message-id");
if (r) prot_printf(nntp_out, " (%s)", error_message(r));
prot_printf(nntp_out, "\r\n");
return -1;
badrange:
prot_printf(nntp_out, "501 Bad message-id or range\r\n");
return -1;
}
static const int numdays[] = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
#define isleap(year) (!((year) % 4) && (((year) % 100) || !((year) % 400)))
static time_t parse_datetime(char *datestr, char *timestr, char *gmt)
{
int datelen = strlen(datestr), leapday;
unsigned long d, t;
char *p;
struct tm tm;
if ((datelen != 6 && datelen != 8) ||
strlen(timestr) != 6 || (gmt && strcasecmp(gmt, "GMT")))
return -1;
d = strtoul(datestr, &p, 10);
if (d == ULONG_MAX || *p) return -1;
t = strtoul(timestr, &p, 10);
if (t == ULONG_MAX || *p) return -1;
tm.tm_year = d / 10000;
d %= 10000;
tm.tm_mon = d / 100 - 1;
tm.tm_mday = d % 100;
tm.tm_hour = t / 10000;
t %= 10000;
tm.tm_min = t / 100;
tm.tm_sec = t % 100;
if (tm.tm_year > 99) tm.tm_year -= 1900;
else {
time_t now = time(NULL);
struct tm *current;
int century;
current = gmt ? gmtime(&now) : localtime(&now);
century = current->tm_year / 100;
if (tm.tm_year > current->tm_year % 100) century--;
tm.tm_year += century * 100;
}
leapday = tm.tm_mon == 1 && isleap(tm.tm_year + 1900);
if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 11 ||
tm.tm_mday < 1 || tm.tm_mday > (numdays[tm.tm_mon] + leapday) ||
tm.tm_hour > 23 || tm.tm_min > 59 || tm.tm_sec > 60)
return -1;
return (gmt ? mkgmtime(&tm) : mktime(&tm));
}
static int open_group(char *name, int has_prefix, struct backend **ret,
int *postable )
{
char mailboxname[MAX_MAILBOX_NAME+1];
int r = 0;
char *acl, *newserver;
struct backend *backend_next = NULL;
if (nntp_group) {
mailbox_close(nntp_group);
nntp_group = 0;
}
if (!has_prefix) {
snprintf(mailboxname, sizeof(mailboxname), "%s%s", newsprefix, name);
name = mailboxname;
}
if (!r) r = mlookup(name, &newserver, &acl, NULL);
if (!r && acl) {
int myrights = cyrus_acl_myrights(nntp_authstate, acl);
if (postable) *postable = myrights & ACL_POST;
if (!postable &&
!(myrights & ACL_READ)) {
r = (myrights & ACL_LOOKUP) ?
IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT;
}
}
if (r) return r;
if (newserver) {
backend_next = proxyd_findserver(newserver);
if (!backend_next) return IMAP_SERVER_UNAVAILABLE;
*ret = backend_next;
}
else {
int doclose = 0;
r = mailbox_open_header(name, nntp_authstate, &mboxstruct);
if (!r) {
doclose = 1;
r = mailbox_open_index(&mboxstruct);
}
if (r) {
if (doclose) mailbox_close(&mboxstruct);
return r;
}
nntp_group = &mboxstruct;
index_operatemailbox(nntp_group);
if (ret) *ret = NULL;
}
syslog(LOG_DEBUG, "open: user %s opened %s",
nntp_userid ? nntp_userid : "anonymous", name);
return 0;
}
static void cmd_capabilities(char *keyword __attribute__((unused)))
{
const char *mechlist;
unsigned mechcount = 0;
prot_printf(nntp_out, "101 Capability list follows:\r\n");
prot_printf(nntp_out, "VERSION 2\r\n");
prot_printf(nntp_out,
"IMPLEMENTATION Cyrus NNTP%s server %s\r\n",
config_mupdate_server ? " Murder" : "", CYRUS_VERSION);
if (tls_enabled() && !nntp_starttls_done && !nntp_authstate)
prot_printf(nntp_out, "STARTTLS\r\n");
sasl_listmech(nntp_saslconn, NULL, "SASL ", " ", "\r\n",
&mechlist, NULL, &mechcount);
if (!nntp_authstate) {
prot_printf(nntp_out, "AUTHINFO%s%s\r\n",
(nntp_starttls_done ||
config_getswitch(IMAPOPT_ALLOWPLAINTEXT)) ?
" USER" : "", mechcount ? " SASL" : "");
}
if (mechcount) prot_printf(nntp_out, "%s", mechlist);
if ((nntp_capa & MODE_READ) && (nntp_userid || allowanonymous)) {
prot_printf(nntp_out, "READER POST LISTGROUP\r\n");
prot_printf(nntp_out, "HDR\r\n");
prot_printf(nntp_out, "OVER\r\n");
prot_printf(nntp_out, "XPAT\r\n");
}
if (nntp_capa & MODE_FEED) {
prot_printf(nntp_out, "IHAVE\r\n");
prot_printf(nntp_out, "STREAMING\r\n");
}
prot_printf(nntp_out, "LIST ACTIVE%s\r\n",
((nntp_capa & MODE_READ) && (nntp_userid || allowanonymous)) ?
" HEADERS NEWSGROUPS OVERVIEW.FMT" : "");
prot_printf(nntp_out, ".\r\n");
did_capabilities = 1;
}
struct xref_rock {
char *buf;
size_t size;
};
static int xref_cb(const char *msgid __attribute__((unused)),
const char *mailbox,
time_t mark __attribute__((unused)),
unsigned long uid, void *rock)
{
struct xref_rock *xrock = (struct xref_rock *) rock;
size_t len = strlen(xrock->buf);
if (*mailbox && !strncmp(mailbox, newsprefix, strlen(newsprefix)) &&
strncmp(mailbox, "user.", 5)) {
snprintf(xrock->buf + len, xrock->size - len,
" %s:%lu", mailbox + strlen(newsprefix), uid);
}
return 0;
}
static void build_xref(char *msgid, char *buf, size_t size, int body_only)
{
struct xref_rock xrock = { buf, size };
snprintf(buf, size, "%s%s", body_only ? "" : "Xref: ", config_servername);
duplicate_find(msgid, &xref_cb, &xrock);
}
static void cmd_article(int part, char *msgid, unsigned long uid)
{
int msgno, by_msgid = (msgid != NULL);
char fname[MAX_MAILBOX_PATH+1];
FILE *msgfile;
msgno = index_finduid(uid);
if (!msgno || index_getuid(msgno) != uid) {
prot_printf(nntp_out, "423 No such article in this newsgroup\r\n");
return;
}
strlcpy(fname, nntp_group->path, sizeof(fname));
strlcat(fname, "/", sizeof(fname));
mailbox_message_get_fname(nntp_group, uid, fname + strlen(fname),
sizeof(fname) - strlen(fname));
msgfile = fopen(fname, "r");
if (!msgfile) {
prot_printf(nntp_out, "502 Could not read message file\r\n");
return;
}
if (!by_msgid) msgid = index_get_msgid(nntp_group, msgno);
prot_printf(nntp_out, "%u %lu %s\r\n",
220 + part, by_msgid ? 0 : uid, msgid ? msgid : "<0>");
if (part != ARTICLE_STAT) {
char buf[4096];
int body = 0;
int output = (part != ARTICLE_BODY);
while (fgets(buf, sizeof(buf), msgfile)) {
if (!body && buf[0] == '\r' && buf[1] == '\n') {
body = 1;
if (output) {
char xref[8192];
build_xref(msgid, xref, sizeof(xref), 0);
prot_printf(nntp_out, "%s\r\n", xref);
}
if (part == ARTICLE_HEAD) {
break;
}
else if (part == ARTICLE_BODY) {
output = 1;
continue;
}
}
if (output) {
if (buf[0] == '.') prot_putc('.', nntp_out);
do {
prot_printf(nntp_out, "%s", buf);
} while (buf[strlen(buf)-1] != '\n' &&
fgets(buf, sizeof(buf), msgfile));
}
}
if (buf[strlen(buf)-1] != '\n') prot_printf(nntp_out, "\r\n");
prot_printf(nntp_out, ".\r\n");
}
if (!by_msgid) free(msgid);
fclose(msgfile);
}
static void cmd_authinfo_user(char *user)
{
char *p;
if (nntp_authstate) {
prot_printf(nntp_out, "502 Already authenticated\r\n");
return;
}
if (!(nntp_starttls_done || config_getswitch(IMAPOPT_ALLOWPLAINTEXT))) {
prot_printf(nntp_out,
"483 AUTHINFO USER command only available under a layer\r\n");
return;
}
if (nntp_userid) {
prot_printf(nntp_out, "502 Must give AUTHINFO PASS command\r\n");
return;
}
if (!(p = canonify_userid(user, NULL, NULL))) {
prot_printf(nntp_out, "502 Invalid user\r\n");
syslog(LOG_NOTICE,
"badlogin: %s plaintext %s invalid user",
nntp_clienthost, beautify_string(user));
}
else {
nntp_userid = xstrdup(p);
prot_printf(nntp_out, "381 Give AUTHINFO PASS command\r\n");
}
}
static void cmd_authinfo_pass(char *pass)
{
if (nntp_authstate) {
prot_printf(nntp_out, "502 Already authenticated\r\n");
return;
}
if (!nntp_userid) {
prot_printf(nntp_out, "482 Must give AUTHINFO USER command first\r\n");
return;
}
if (!strcmp(nntp_userid, "anonymous")) {
if (allowanonymous) {
pass = beautify_string(pass);
if (strlen(pass) > 500) pass[500] = '\0';
syslog(LOG_NOTICE, "login: %s anonymous %s",
nntp_clienthost, pass);
}
else {
syslog(LOG_NOTICE, "badlogin: %s anonymous login refused",
nntp_clienthost);
prot_printf(nntp_out, "502 Invalid login\r\n");
return;
}
}
else if (sasl_checkpass(nntp_saslconn,
nntp_userid,
strlen(nntp_userid),
pass,
strlen(pass))!=SASL_OK) {
syslog(LOG_NOTICE, "badlogin: %s plaintext %s %s",
nntp_clienthost, nntp_userid, sasl_errdetail(nntp_saslconn));
sleep(3);
prot_printf(nntp_out, "502 Invalid login\r\n");
free(nntp_userid);
nntp_userid = 0;
return;
}
else {
syslog(LOG_NOTICE, "login: %s %s plaintext%s %s", nntp_clienthost,
nntp_userid, nntp_starttls_done ? "+TLS" : "",
"User logged in");
prot_printf(nntp_out, "281 User logged in\r\n");
nntp_authstate = auth_newstate(nntp_userid);
nntp_logfd = telemetry_log(nntp_userid, nntp_in, nntp_out, 0);
}
}
static void cmd_authinfo_sasl(char *cmd, char *mech, char *resp)
{
int r, sasl_result;
char *success_data;
const int *ssfp;
char *ssfmsg = NULL;
const char *canon_user;
if (nntp_userid) {
prot_printf(nntp_out, "502 Already authenticated\r\n");
return;
}
if (cmd[0] == 'g') {
if (!mech) {
const char *sasllist;
unsigned int mechnum;
prot_printf(nntp_out, "281 List of mechanisms follows\r\n");
if (sasl_listmech(nntp_saslconn, NULL,
"", "\r\n", "\r\n",
&sasllist,
NULL, &mechnum) == SASL_OK) {
if (mechnum > 0) {
prot_printf(nntp_out, "%s", sasllist);
}
}
prot_printf(nntp_out, ".\r\n");
return;
}
r = saslserver(nntp_saslconn, mech, resp, "AUTHINFO GENERIC ", "381 ",
"", nntp_in, nntp_out, &sasl_result, &success_data);
}
else
r = saslserver(nntp_saslconn, mech, resp, "", "383 ", "=",
nntp_in, nntp_out, &sasl_result, &success_data);
if (r) {
int code;
const char *errorstring = NULL;
switch (r) {
case IMAP_SASL_CANCEL:
prot_printf(nntp_out,
"481 Client canceled authentication\r\n");
break;
case IMAP_SASL_PROTERR:
errorstring = prot_error(nntp_in);
prot_printf(nntp_out,
"482 Error reading client response: %s\r\n",
errorstring ? errorstring : "");
break;
default:
switch (sasl_result) {
case SASL_NOMECH:
case SASL_TOOWEAK:
code = 501;
break;
case SASL_ENCRYPT:
code = 483;
break;
case SASL_BADPROT:
code = 482;
break;
default:
code = 481;
}
errorstring = sasl_errstring(sasl_result, NULL, NULL);
syslog(LOG_NOTICE, "badlogin: %s %s [%s]",
nntp_clienthost, mech, sasl_errdetail(nntp_saslconn));
sleep(3);
if (errorstring) {
prot_printf(nntp_out, "%d %s\r\n", code, errorstring);
} else {
prot_printf(nntp_out, "%d Error authenticating\r\n", code);
}
}
reset_saslconn(&nntp_saslconn);
return;
}
sasl_result = sasl_getprop(nntp_saslconn, SASL_USERNAME,
(const void **) &canon_user);
nntp_userid = xstrdup(canon_user);
if (sasl_result != SASL_OK) {
prot_printf(nntp_out, "481 weird SASL error %d SASL_USERNAME\r\n",
sasl_result);
syslog(LOG_ERR, "weird SASL error %d getting SASL_USERNAME",
sasl_result);
reset_saslconn(&nntp_saslconn);
return;
}
proc_register("nntpd", nntp_clienthost, nntp_userid, (char *)0);
syslog(LOG_NOTICE, "login: %s %s %s%s %s", nntp_clienthost, nntp_userid,
mech, nntp_starttls_done ? "+TLS" : "", "User logged in");
sasl_getprop(nntp_saslconn, SASL_SSF, (const void **) &ssfp);
if (nntp_starttls_done) {
switch(*ssfp) {
case 0: ssfmsg = "tls protection"; break;
case 1: ssfmsg = "tls plus integrity protection"; break;
default: ssfmsg = "tls plus privacy protection"; break;
}
} else {
switch(*ssfp) {
case 0: ssfmsg = "no protection"; break;
case 1: ssfmsg = "integrity protection"; break;
default: ssfmsg = "privacy protection"; break;
}
}
if (success_data) {
prot_printf(nntp_out, "283 %s\r\n", success_data);
free(success_data);
} else {
prot_printf(nntp_out, "281 Success (%s)\r\n", ssfmsg);
}
prot_setsasl(nntp_in, nntp_saslconn);
prot_setsasl(nntp_out, nntp_saslconn);
nntp_logfd = telemetry_log(nntp_userid, nntp_in, nntp_out, 0);
}
static void cmd_hdr(char *cmd, char *hdr, char *pat, char *msgid,
unsigned long uid, unsigned long last)
{
lcase(hdr);
prot_printf(nntp_out, "%u Headers follow:\r\n", cmd[0] == 'X' ? 221 : 225);
for (; uid <= last; uid++) {
char *body;
int msgno = index_finduid(uid);
int by_msgid = (msgid != NULL);
if (!msgno || index_getuid(msgno) != uid) continue;
if (hdr[0] == ':') {
if (!strcasecmp(":size", hdr)) {
char xref[8192];
unsigned long size = index_getsize(nntp_group, msgno);
if (!by_msgid) msgid = index_get_msgid(nntp_group, msgno);
build_xref(msgid, xref, sizeof(xref), 0);
if (!by_msgid) free(msgid);
prot_printf(nntp_out, "%lu %lu\r\n", by_msgid ? 0 : uid,
size + strlen(xref) + 2);
}
else if (!strcasecmp(":lines", hdr))
prot_printf(nntp_out, "%lu %lu\r\n", by_msgid ? 0 : uid,
index_getlines(nntp_group, msgno));
else
prot_printf(nntp_out, "%lu \r\n", by_msgid ? 0 : uid);
}
else if (!strcmp(hdr, "xref") && !pat ) {
char xref[8192];
if (!by_msgid) msgid = index_get_msgid(nntp_group, msgno);
build_xref(msgid, xref, sizeof(xref), 1);
if (!by_msgid) free(msgid);
prot_printf(nntp_out, "%lu %s\r\n", by_msgid ? 0 : uid, xref);
}
else if ((body = index_getheader(nntp_group, msgno, hdr)) &&
(!pat ||
wildmat(body, pat))) {
prot_printf(nntp_out, "%lu %s\r\n", by_msgid ? 0 : uid, body);
}
}
prot_printf(nntp_out, ".\r\n");
}
static void cmd_help(void)
{
prot_printf(nntp_out, "100 Supported commands:\r\n");
if ((nntp_capa & MODE_READ) && (nntp_userid || allowanonymous)) {
prot_printf(nntp_out, "\tARTICLE [ message-id | number ]\r\n"
"\t\tRetrieve entirety of the specified article.\r\n");
}
if (!nntp_authstate) {
if (!nntp_userid) {
prot_printf(nntp_out, "\tAUTHINFO SASL mechanism [initial-response]\r\n"
"\t\tPerform an authentication exchange using the specified\r\n"
"\t\tSASL mechanism.\r\n");
prot_printf(nntp_out, "\tAUTHINFO USER username\r\n"
"\t\tPresent username for authentication.\r\n");
}
prot_printf(nntp_out, "\tAUTHINFO PASS password\r\n"
"\t\tPresent clear-text password for authentication.\r\n");
}
if ((nntp_capa & MODE_READ) && (nntp_userid || allowanonymous)) {
prot_printf(nntp_out, "\tBODY [ message-id | number ]\r\n"
"\t\tRetrieve body of the specified article.\r\n");
}
prot_printf(nntp_out, "\tCAPABILITIES\r\n"
"\t\tList the current server capabilities.\r\n");
if (nntp_capa & MODE_FEED) {
prot_printf(nntp_out, "\tCHECK message-id\r\n"
"\t\tCheck if the server wants the specified article.\r\n");
}
if ((nntp_capa & MODE_READ) && (nntp_userid || allowanonymous)) {
prot_printf(nntp_out, "\tDATE\r\n"
"\t\tRequest the current server UTC date and time.\r\n");
prot_printf(nntp_out, "\tGROUP group\r\n"
"\t\tSelect a newsgroup for article retrieval.\r\n");
prot_printf(nntp_out, "\tHDR header [ message-id | range ]\r\n"
"\t\tRetrieve the specified header/metadata from the\r\n"
"\t\tspecified article(s).\r\n");
}
prot_printf(nntp_out, "\tHEAD [ message-id | number ]\r\n"
"\t\tRetrieve the headers of the specified article.\r\n");
prot_printf(nntp_out, "\tHELP\r\n"
"\t\tRequest command summary (this text).\r\n");
if (nntp_capa & MODE_FEED) {
prot_printf(nntp_out, "\tIHAVE message-id\r\n"
"\t\tPresent/transfer the specified article to the server.\r\n");
}
if ((nntp_capa & MODE_READ) && (nntp_userid || allowanonymous)) {
prot_printf(nntp_out, "\tLAST\r\n"
"\t\tSelect the previous article.\r\n");
}
prot_printf(nntp_out, "\tLIST [ ACTIVE wildmat ]\r\n"
"\t\tList the (subset of) valid newsgroups.\r\n");
if ((nntp_capa & MODE_READ) && (nntp_userid || allowanonymous)) {
prot_printf(nntp_out, "\tLIST HEADERS [ MSGID | RANGE ]\r\n"
"\t\tList the headers and metadata items available via HDR.\r\n");
prot_printf(nntp_out, "\tLIST NEWSGROUPS [wildmat]\r\n"
"\t\tList the descriptions of the specified newsgroups.\r\n");
prot_printf(nntp_out, "\tLIST OVERVIEW.FMT\r\n"
"\t\tList the headers and metadata items available via OVER.\r\n");
}
if ((nntp_capa & MODE_READ) && (nntp_userid || allowanonymous)) {
prot_printf(nntp_out, "\tLISTGROUP [group]\r\n"
"\t\tList the article numbers in the specified newsgroup.\r\n");
if (config_getswitch(IMAPOPT_ALLOWNEWNEWS))
prot_printf(nntp_out, "\tNEWNEWS wildmat date time [GMT]\r\n"
"\t\tList the newly arrived articles in the specified newsgroup(s)\r\n"
"\t\tsince the specified date and time.\r\n");
prot_printf(nntp_out, "\tNEXT\r\n"
"\t\tSelect the next article.\r\n");
prot_printf(nntp_out, "\tOVER [ message-id | range ]\r\n"
"\t\tRetrieve the overview information for the specified article(s).\r\n");
prot_printf(nntp_out, "\tPOST\r\n"
"\t\tPost an article to the server.\r\n");
}
prot_printf(nntp_out, "\tQUIT\r\n"
"\t\tTerminate the session.\r\n");
if (tls_enabled() && !nntp_starttls_done && !nntp_authstate) {
prot_printf(nntp_out, "\tSTARTTLS\r\n"
"\t\tStart a TLS negotiation.\r\n");
}
prot_printf(nntp_out, "\tSTAT [ message-id | number ]\r\n"
"\t\tCheck if the specified article exists.\r\n");
if (nntp_capa & MODE_FEED) {
prot_printf(nntp_out, "\tTAKETHIS message-id\r\n"
"\t\tTransfer the specified article to the server.\r\n");
}
if ((nntp_capa & MODE_READ) && (nntp_userid || allowanonymous)) {
prot_printf(nntp_out, "\tXPAT header message-id|range wildmat\r\n"
"\t\tList the specified article(s) in which the contents\r\n"
"\t\tof the specified header/metadata matches the wildmat.\r\n");
}
prot_printf(nntp_out, ".\r\n");
}
struct list_rock {
int (*proc)();
struct wildmat *wild;
struct hash_table server_table;
};
int list_cb(char *name, int matchlen, int maycreate __attribute__((unused)),
void *rock)
{
static char lastname[MAX_MAILBOX_NAME+1];
struct list_rock *lrock = (struct list_rock *) rock;
struct wildmat *wild;
if (!name) {
lastname[0] = '\0';
return 0;
}
if ((!strncasecmp(name, "INBOX", 5) && (!name[5] || name[5] == '.')) ||
!strncmp(name, "user.", 5))
return 0;
if (matchlen == strlen(lastname) &&
!strncmp(name, lastname, matchlen)) return 0;
strncpy(lastname, name, matchlen);
lastname[matchlen] = '\0';
wild = lrock->wild;
while (wild->pat && wildmat(name, wild->pat) != 1) wild++;
if (!wild->pat || wild->not) return 0;
return lrock->proc(name, lrock);
}
struct enum_rock {
const char *cmd;
char *wild;
};
void list_proxy(char *server, void *data __attribute__((unused)), void *rock)
{
struct enum_rock *erock = (struct enum_rock *) rock;
struct backend *be;
int r;
char *result;
be = proxyd_findserver(server);
if (!be) return;
prot_printf(be->out, "LIST %s %s\r\n", erock->cmd, erock->wild);
r = read_response(be, 0, &result);
if (!r && !strncmp(result, "215 ", 4)) {
while (!(r = read_response(be, 0, &result)) && result[0] != '.') {
prot_printf(nntp_out, "%s", result);
}
}
}
int do_active(char *name, void *rock)
{
struct list_rock *lrock = (struct list_rock *) rock;
int r, postable;
struct backend *be;
r = open_group(name, 1, &be, &postable);
if (r) {
}
else if (be) {
if (!hash_lookup(be->hostname, &lrock->server_table)) {
hash_insert(be->hostname, (void *)0xDEADBEEF, &lrock->server_table);
}
}
else {
prot_printf(nntp_out, "%s %lu %lu %c\r\n", name+strlen(newsprefix),
nntp_group->exists ? index_getuid(nntp_group->exists) :
nntp_group->last_uid,
nntp_group->exists ? index_getuid(1) :
nntp_group->last_uid+1,
postable ? 'y' : 'n');
mailbox_close(nntp_group);
nntp_group = 0;
}
return 0;
}
int do_newsgroups(char *name, void *rock)
{
struct list_rock *lrock = (struct list_rock *) rock;
char *acl, *server;
int r;
r = mlookup(name, &server, &acl, NULL);
if (r || !acl || !(cyrus_acl_myrights(nntp_authstate, acl) && ACL_LOOKUP))
return 0;
if (server) {
if (!hash_lookup(server, &lrock->server_table)) {
hash_insert(server, (void *)0xDEADBEEF, &lrock->server_table);
}
}
else {
return CYRUSDB_DONE;
}
return 0;
}
int newsgroups_cb(const char *mailbox,
const char *entry __attribute__((unused)),
const char *userid,
struct annotation_data *attrib, void *rock)
{
struct wildmat *wild = (struct wildmat *) rock;
if ((!strncasecmp(mailbox, "INBOX", 5) &&
(!mailbox[5] || mailbox[5] == '.')) ||
!strncmp(mailbox, "user.", 5))
return 0;
while (wild->pat && wildmat(mailbox, wild->pat) != 1) wild++;
if (!wild->pat || wild->not) return 0;
if (userid[0]) return 0;
prot_printf(nntp_out, "%s\t%s\r\n", mailbox+strlen(newsprefix),
attrib->value);
return 0;
}
static void cmd_list(char *arg1, char *arg2)
{
if (!arg1)
arg1 = "active";
else
lcase(arg1);
if (!strcmp(arg1, "active")) {
char pattern[MAX_MAILBOX_NAME+1];
struct list_rock lrock;
struct enum_rock erock;
if (!arg2) arg2 = "*";
erock.cmd = "ACTIVE";
erock.wild = xstrdup(arg2);
lrock.proc = do_active;
lrock.wild = split_wildmats(arg2);
construct_hash_table(&lrock.server_table, 10, 1);
prot_printf(nntp_out, "215 List of newsgroups follows:\r\n");
strcpy(pattern, newsprefix);
strcat(pattern, "*");
list_cb(NULL, 0, 0, NULL);
mboxlist_findall(NULL, pattern, 0, nntp_userid, nntp_authstate,
list_cb, &lrock);
hash_enumerate(&lrock.server_table, list_proxy, &erock);
prot_printf(nntp_out, ".\r\n");
free_hash_table(&lrock.server_table, NULL);
free_wildmats(lrock.wild);
free(erock.wild);
if (nntp_group) {
mailbox_close(nntp_group);
nntp_group = 0;
}
}
else if (!(nntp_capa & MODE_READ)) {
prot_printf(nntp_out, "502 Permission denied\r\n");
return;
}
else if (!nntp_userid && !allowanonymous) {
prot_printf(nntp_out, "480 Authentication required\r\n");
return;
}
else if (!strcmp(arg1, "headers")) {
if (arg2 && strcmp(arg2, "msgid") && strcmp(arg2, "range")) {
prot_printf(nntp_out, "501 Unexpected extra argument\r\n");
return;
}
prot_printf(nntp_out, "215 Header and metadata list follows:\r\n");
prot_printf(nntp_out, ":\r\n");
prot_printf(nntp_out, ":bytes\r\n");
prot_printf(nntp_out, ":lines\r\n");
prot_printf(nntp_out, ".\r\n");
}
else if (!strcmp(arg1, "newsgroups")) {
char pattern[MAX_MAILBOX_NAME+1];
struct list_rock lrock;
struct enum_rock erock;
if (!arg2) arg2 = "*";
erock.cmd = "NEWSGROUPS";
erock.wild = xstrdup(arg2);
lrock.proc = do_newsgroups;
lrock.wild = split_wildmats(arg2);
construct_hash_table(&lrock.server_table, 10, 1);
prot_printf(nntp_out, "215 List of newsgroups follows:\r\n");
strcpy(pattern, newsprefix);
strcat(pattern, "*");
list_cb(NULL, 0, 0, NULL);
mboxlist_findall(NULL, pattern, 0, nntp_userid, nntp_authstate,
list_cb, &lrock);
hash_enumerate(&lrock.server_table, list_proxy, &erock);
strcpy(pattern, newsprefix);
strcat(pattern, "*");
annotatemore_findall(pattern, "/comment",
newsgroups_cb, lrock.wild, NULL);
prot_printf(nntp_out, ".\r\n");
free_hash_table(&lrock.server_table, NULL);
free_wildmats(lrock.wild);
free(erock.wild);
}
else if (!strcmp(arg1, "overview.fmt")) {
if (arg2) {
prot_printf(nntp_out, "501 Unexpected extra argument\r\n");
return;
}
prot_printf(nntp_out, "215 Order of overview fields follows:\r\n");
prot_printf(nntp_out, "Subject:\r\n");
prot_printf(nntp_out, "From:\r\n");
prot_printf(nntp_out, "Date:\r\n");
prot_printf(nntp_out, "Message-ID:\r\n");
prot_printf(nntp_out, "References:\r\n");
if (did_capabilities) {
prot_printf(nntp_out, ":bytes\r\n");
prot_printf(nntp_out, ":lines\r\n");
} else {
prot_printf(nntp_out, "Bytes:\r\n");
prot_printf(nntp_out, "Lines:\r\n");
}
prot_printf(nntp_out, "Xref:full\r\n");
prot_printf(nntp_out, ".\r\n");
}
else if (!strcmp(arg1, "active.times") || !strcmp(arg1, "distributions") ||
!strcmp(arg1, "distrib.pats")) {
prot_printf(nntp_out, "503 Unsupported LIST command\r\n");
}
else {
prot_printf(nntp_out, "501 Unrecognized LIST command\r\n");
}
prot_flush(nntp_out);
}
static void cmd_mode(char *arg)
{
lcase(arg);
if (!strcmp(arg, "reader")) {
prot_printf(nntp_out,
"%u %s Cyrus NNTP%s %s server ready, posting %s\r\n",
(nntp_capa & MODE_READ) ? 200 : 201,
config_servername, config_mupdate_server ? " Murder" : "",
CYRUS_VERSION,
(nntp_capa & MODE_READ) ? "allowed" : "prohibited");
}
else if (!strcmp(arg, "stream")) {
if (nntp_capa & MODE_FEED) {
prot_printf(nntp_out, "203 Streaming allowed\r\n");
}
else {
prot_printf(nntp_out, "502 Streaming prohibited\r\n");
}
}
else {
prot_printf(nntp_out, "501 Unrecognized MODE\r\n");
}
prot_flush(nntp_out);
}
static void cmd_newgroups(time_t tstamp __attribute__((unused)))
{
prot_printf(nntp_out, "503 Can't determine NEWGROUPS at this time\r\n");
#if 0
prot_printf(nntp_out, "231 List of new newsgroups follows:\r\n");
prot_printf(nntp_out, ".\r\n");
#endif
}
struct newrock {
time_t tstamp;
struct wildmat *wild;
};
static int newnews_cb(const char *msgid, const char *rcpt, time_t mark,
unsigned long uid, void *rock)
{
static char lastid[1024];
struct newrock *nrock = (struct newrock *) rock;
if (!msgid) {
lastid[0] = '\0';
return 0;
}
if (strcmp(msgid, lastid) && mark >= nrock->tstamp &&
uid && rcpt[0] && strncmp(rcpt, "user.", 5)) {
struct wildmat *wild = nrock->wild;
strlcpy(lastid, msgid, sizeof(lastid));
while (wild->pat && wildmat(rcpt, wild->pat) != 1) wild++;
if (wild->pat && !wild->not)
prot_printf(nntp_out, "%s\r\n", msgid);
}
return 0;
}
static void cmd_newnews(char *wild, time_t tstamp)
{
struct newrock nrock;
nrock.tstamp = tstamp;
nrock.wild = split_wildmats(wild);
prot_printf(nntp_out, "230 List of new articles follows:\r\n");
newnews_cb(NULL, NULL, 0, 0, NULL);
duplicate_find("", &newnews_cb, &nrock);
prot_printf(nntp_out, ".\r\n");
free_wildmats(nrock.wild);
}
static void cmd_over(char *msgid, unsigned long uid, unsigned long last)
{
int msgno;
struct nntp_overview *over;
int found = 0;
for (; uid <= last; uid++) {
msgno = index_finduid(uid);
if (!msgno || index_getuid(msgno) != uid) continue;
if ((over = index_overview(nntp_group, msgno))) {
char xref[8192];
build_xref(over->msgid, xref, sizeof(xref), 0);
if (!found++)
prot_printf(nntp_out, "224 Overview information follows:\r\n");
prot_printf(nntp_out, "%lu\t%s\t%s\t%s\t%s\t%s\t%lu\t%lu\t%s\r\n",
msgid ? 0 : over->uid,
over->subj ? over->subj : "",
over->from ? over->from : "",
over->date ? over->date : "",
over->msgid ? over->msgid : "",
over->ref ? over->ref : "",
over->bytes + strlen(xref) + 2,
over->lines, xref);
}
}
if (found)
prot_printf(nntp_out, ".\r\n");
else
prot_printf(nntp_out, "420 No articles selected\r\n");
}
#define RCPT_GROW 30
typedef struct message_data message_data_t;
struct message_data {
struct protstream *data;
FILE *f;
char *id;
char *path;
char *control;
unsigned long size;
char **rcpt;
int rcpt_num;
hdrcache_t hdrcache;
};
int msg_new(message_data_t **m)
{
message_data_t *ret = (message_data_t *) xmalloc(sizeof(message_data_t));
ret->data = NULL;
ret->f = NULL;
ret->id = NULL;
ret->path = NULL;
ret->control = NULL;
ret->size = 0;
ret->rcpt = NULL;
ret->rcpt_num = 0;
ret->hdrcache = spool_new_hdrcache();
*m = ret;
return 0;
}
void msg_free(message_data_t *m)
{
int i;
if (m->data) {
prot_free(m->data);
}
if (m->f) {
fclose(m->f);
}
if (m->id) {
free(m->id);
}
if (m->path) {
free(m->path);
}
if (m->control) {
free(m->control);
}
if (m->rcpt) {
for (i = 0; i < m->rcpt_num; i++) {
free(m->rcpt[i]);
}
free(m->rcpt);
}
spool_free_hdrcache(m->hdrcache);
free(m);
}
static int parse_groups(const char *groups, message_data_t *msg)
{
const char *p;
char *rcpt = NULL;
size_t n;
for (p = groups;; p += n) {
while (p && *p && (isspace((int) *p) || *p == ',')) p++;
if (!p || !*p) return 0;
if (!(msg->rcpt_num % RCPT_GROW)) {
msg->rcpt = (char **)
xrealloc(msg->rcpt, (msg->rcpt_num + RCPT_GROW + 1) *
sizeof(char *));
}
n = strcspn(p, ", \t");
rcpt = xrealloc(rcpt, strlen(newsprefix) + n + 1);
if (!rcpt) return -1;
sprintf(rcpt, "%s%.*s", newsprefix, n, p);
if (!mlookup(rcpt, NULL, NULL, NULL)) {
msg->rcpt[msg->rcpt_num] = rcpt;
msg->rcpt_num++;
msg->rcpt[msg->rcpt_num] = rcpt = NULL;
}
}
return NNTP_FAIL_NEWSGROUPS;
}
static int savemsg(message_data_t *m, FILE *f)
{
struct stat sbuf;
const char **body, **groups;
int r, i;
time_t now = time(NULL);
static int post_count = 0;
FILE *stagef = NULL;
const char *skipheaders[] = {
"Path",
"Xref",
"Reply-To",
NULL
};
int addlen;
r = spool_fill_hdrcache(nntp_in, f, m->hdrcache, skipheaders);
if (r) {
spool_copy_msg(nntp_in, NULL);
return r;
}
addlen = strlen(config_servername) + 1;
if ((body = spool_getheader(m->hdrcache, "path")) != NULL) {
addlen += strlen(body[0]);
body[0] = xrealloc((char *) body[0], addlen + 1);
memmove((char *) body[0] + strlen(config_servername) + 1, body[0],
strlen(body[0]) + 1);
strcpy((char *) body[0], config_servername);
*((char *) body[0] + strlen(config_servername)) = '!';
m->path = xstrdup(body[0]);
} else {
addlen += nntp_userid ? strlen(nntp_userid) : strlen("anonymous");
m->path = xmalloc(addlen + 1);
sprintf(m->path, "%s!%s", config_servername,
nntp_userid ? nntp_userid : "anonymous");
spool_cache_header(xstrdup("Path"), xstrdup(m->path), m->hdrcache);
}
fprintf(f, "Path: %s\r\n", m->path);
if ((body = spool_getheader(m->hdrcache, "message-id")) != NULL) {
m->id = xstrdup(body[0]);
} else {
pid_t p = getpid();
m->id = xmalloc(40 + strlen(config_servername));
sprintf(m->id, "<cmu-nntpd-%d-%d-%d@%s>", p, (int) now,
post_count++, config_servername);
fprintf(f, "Message-ID: %s\r\n", m->id);
spool_cache_header(xstrdup("Message-ID"), xstrdup(m->id), m->hdrcache);
}
if ((body = spool_getheader(m->hdrcache, "date")) == NULL) {
char datestr[80];
rfc822date_gen(datestr, sizeof(datestr), now);
fprintf(f, "Date: %s\r\n", datestr);
spool_cache_header(xstrdup("Date"), xstrdup(datestr), m->hdrcache);
}
if ((body = spool_getheader(m->hdrcache, "control")) != NULL) {
int len;
m->control = xstrdup(body[0]);
m->rcpt_num = 1;
m->rcpt = (char **) xmalloc(sizeof(char *));
len = strcspn(m->control, " \t\r\n");
m->rcpt[0] = xmalloc(strlen(newsprefix) + 8 + len + 1);
sprintf(m->rcpt[0], "%scontrol.%.*s", newsprefix, len, m->control);
} else {
m->control = NULL;
if ((groups = spool_getheader(m->hdrcache, "newsgroups")) != NULL) {
r = parse_groups(groups[0], m);
if (!r && !m->rcpt_num) {
r = IMAP_MAILBOX_NONEXISTENT;
}
if (!r) {
const char *newspostuser = config_getstring(IMAPOPT_NEWSPOSTUSER);
body = spool_getheader(m->hdrcache, "reply-to");
if (body || newspostuser) {
const char **postto, *p;
char *replyto, *r, *fold = NULL, *sep = "";
size_t n;
if (newspostuser) {
postto = spool_getheader(m->hdrcache, "followup-to");
if (!postto) postto = groups;
for (n = 0, p = postto[0]; p; n++) {
p = strchr(p, ',');
if (p) p++;
}
addlen = strlen(postto[0]) +
n * (strlen(newspostuser) + 3);
if (body) {
addlen += strlen(body[0]);
body[0] = xrealloc((char *) body[0], addlen + 1);
replyto = (char *) body[0];
fold = replyto + strlen(replyto) + 1;
sep = ", ";
}
else {
replyto = xzmalloc(addlen + 1);
}
r = replyto + strlen(replyto);
for (p = postto[0];; p += n) {
while (p && *p &&
(isspace((int) *p) || *p == ',')) p++;
if (!p || !*p) break;
n = strcspn(p, ", \t");
r += sprintf(r, "%s%s+%.*s",
sep, newspostuser, n, p);
sep = ", ";
}
if (!body) {
spool_cache_header(xstrdup("Reply-To"), replyto,
m->hdrcache);
}
} else {
replyto = (char *) body[0];
}
fprintf(f, "Reply-To: ");
r = replyto;
if (fold) {
fprintf(f, "%.*s\r\n\t", fold - r, r);
r = fold;
}
fprintf(f, "%s\r\n", r);
}
}
} else {
r = NNTP_NO_NEWSGROUPS;
}
if (r) {
spool_copy_msg(nntp_in, NULL);
return r;
}
}
fflush(f);
if (ferror(f)) {
return IMAP_IOERROR;
}
if (fstat(fileno(f), &sbuf) == -1) {
return IMAP_IOERROR;
}
for (i = 0; !stagef && (i < m->rcpt_num); i++) {
stagef = append_newstage(m->rcpt[i], now, 0, &stage);
}
if (stagef) {
const char *base = 0;
unsigned long size = 0;
int n;
map_refresh(fileno(f), 1, &base, &size, sbuf.st_size, "tmp", 0);
n = retry_write(fileno(stagef), base, size);
map_free(&base, &size);
if (n == -1) {
fclose(stagef);
append_removestage(stage);
stage = NULL;
return IMAP_IOERROR;
}
else {
fclose(f);
f = stagef;
}
}
r = spool_copy_msg(nntp_in, f);
if (r) return r;
fflush(f);
if (ferror(f)) {
return IMAP_IOERROR;
}
if (fstat(fileno(f), &sbuf) == -1) {
return IMAP_IOERROR;
}
m->size = sbuf.st_size;
m->f = f;
m->data = prot_new(fileno(f), 0);
return 0;
}
static int deliver(message_data_t *msg)
{
int n, r, myrights;
char *rcpt = NULL, *local_rcpt = NULL, *server, *acl;
time_t now = time(NULL);
unsigned long uid, backend_mask = 0;
for (n = 0; n < msg->rcpt_num; n++) {
rcpt = msg->rcpt[n];
r = mlookup(rcpt, &server, &acl, NULL);
if (r) return IMAP_MAILBOX_NONEXISTENT;
if (!(acl && (myrights = cyrus_acl_myrights(nntp_authstate, acl)) &&
(myrights & ACL_POST)))
return IMAP_PERMISSION_DENIED;
if (server) {
struct backend *be = NULL;
unsigned id;
char buf[4096];
be = proxyd_findserver(server);
if (!be) return IMAP_SERVER_UNAVAILABLE;
if ((id = *((unsigned *) be->context)) < 32) {
if (backend_mask & (1 << id)) continue;
backend_mask |= (1 << id);
}
prot_printf(be->out, "IHAVE %s\r\n", msg->id);
prot_flush(be->out);
if (!prot_fgets(buf, sizeof(buf), be->in) ||
strncmp("335", buf, 3)) {
syslog(LOG_NOTICE, "backend doesn't want article %s", msg->id);
continue;
}
rewind(msg->f);
while (fgets(buf, sizeof(buf), msg->f)) {
if (buf[0] == '.') prot_putc('.', be->out);
do {
prot_printf(be->out, "%s", buf);
} while (buf[strlen(buf)-1] != '\n' &&
fgets(buf, sizeof(buf), msg->f));
}
if (buf[strlen(buf)-1] != '\n') prot_printf(be->out, "\r\n");
prot_printf(be->out, ".\r\n");
if (!prot_fgets(buf, sizeof(buf), be->in) ||
strncmp("235", buf, 3)) {
syslog(LOG_WARNING, "article %s transfer to backend failed",
msg->id);
return NNTP_FAIL_TRANSFER;
}
}
else {
struct appendstate as;
if (msg->id &&
duplicate_check(msg->id, strlen(msg->id), rcpt, strlen(rcpt))) {
duplicate_log(msg->id, rcpt, "nntp delivery");
continue;
}
r = append_setup(&as, rcpt, MAILBOX_FORMAT_NORMAL,
nntp_userid, nntp_authstate, ACL_POST, 0);
if (!r) {
prot_rewind(msg->data);
if (stage) {
r = append_fromstage(&as, stage, now,
(const char **) NULL, 0, !singleinstance);
} else {
r = append_fromstream(&as, msg->data, msg->size, now,
(const char **) NULL, 0);
}
if (!r) append_commit(&as, 0, NULL, &uid, NULL);
else append_abort(&as);
}
if (!r && msg->id)
duplicate_mark(msg->id, strlen(msg->id), rcpt, strlen(rcpt),
now, uid);
if (r) return r;
local_rcpt = rcpt;
}
}
return 0;
}
static int newgroup(message_data_t *msg)
{
int r;
char *group;
char mailboxname[MAX_MAILBOX_NAME+1];
group = msg->control + 8;
while (isspace((int) *group)) group++;
snprintf(mailboxname, sizeof(mailboxname), "%s%.*s",
newsprefix, (int) strcspn(group, " \t\r\n"), group);
r = mboxlist_createmailbox(mailboxname, 0, NULL, 0,
newsmaster, newsmaster_authstate, 0, 0, 0);
return r;
}
static int rmgroup(message_data_t *msg)
{
int r;
char *group;
char mailboxname[MAX_MAILBOX_NAME+1];
group = msg->control + 7;
while (isspace((int) *group)) group++;
snprintf(mailboxname, sizeof(mailboxname), "%s%.*s",
newsprefix, (int) strcspn(group, " \t\r\n"), group);
r = mboxlist_deletemailbox(mailboxname, 0,
newsmaster, newsmaster_authstate, 1, 0, 0);
return r;
}
static int mvgroup(message_data_t *msg)
{
int r, len;
char *group;
char oldmailboxname[MAX_MAILBOX_NAME+1];
char newmailboxname[MAX_MAILBOX_NAME+1];
group = msg->control + 7;
while (isspace((int) *group)) group++;
len = (int) strcspn(group, " \t\r\n");
snprintf(oldmailboxname, sizeof(oldmailboxname), "%s%.*s",
newsprefix, len, group);
group += len;
while (isspace((int) *group)) group++;
len = (int) strcspn(group, " \t\r\n");
snprintf(newmailboxname, sizeof(newmailboxname), "%s%.*s",
newsprefix, len, group);
r = mboxlist_renamemailbox(oldmailboxname, newmailboxname, NULL, 0,
newsmaster, newsmaster_authstate);
return r;
}
static int expunge_cancelled(struct mailbox *mailbox __attribute__((unused)),
void *rock, char *index)
{
int uid = ntohl(*((bit32 *)(index+OFFSET_UID)));
return (uid == *((unsigned long *) rock));
}
static int cancel_cb(const char *msgid __attribute__((unused)),
const char *mailbox,
time_t mark __attribute__((unused)),
unsigned long uid,
void *rock)
{
if (*mailbox && !strncmp(mailbox, newsprefix, strlen(newsprefix)) &&
strncmp(mailbox, "user.", 5)) {
struct mailbox mbox;
int r, doclose = 0;
r = mailbox_open_header(mailbox, 0, &mbox);
if (!r &&
!(cyrus_acl_myrights(newsmaster_authstate, mbox.acl) & ACL_DELETE))
r = IMAP_PERMISSION_DENIED;
if (!r) {
doclose = 1;
if (mbox.header_fd != -1)
mailbox_lock_header(&mbox);
mbox.header_lock_count = 1;
r = mailbox_open_index(&mbox);
}
if (!r) {
mailbox_lock_index(&mbox);
mbox.index_lock_count = 1;
mailbox_expunge(&mbox, 0, expunge_cancelled, &uid);
}
if (doclose) mailbox_close(&mbox);
if (r) *((int *) rock) = r;
}
return 0;
}
static int cancel(message_data_t *msg)
{
int r = 0;
char *msgid, *p;
time_t now = time(NULL);
msgid = strchr(msg->control, '<');
p = strrchr(msgid, '>') + 1;
*p = '\0';
duplicate_find(msgid, &cancel_cb, &r);
duplicate_mark(msgid, strlen(msgid), "", 0, 0, now);
return r;
}
static int strip_post_addresses(char *body)
{
const char *newspostuser = config_getstring(IMAPOPT_NEWSPOSTUSER);
char *p, *end;
size_t postlen, n;
int nonpost = 0;
if (!newspostuser) return 1;
postlen = strlen(newspostuser);
for (p = body;; p += n) {
end = p;
while (p && *p && (isspace((int) *p) || *p == ',')) p++;
if (!p || !*p) break;
n = strcspn(p, ", \t\r\n");
if ((n > postlen + 1) &&
!strncmp(p, newspostuser, postlen) && p[postlen] == '+') {
strcpy(end, "\r\n");
break;
}
nonpost = 1;
}
return nonpost;
}
static void feedpeer(char *peer, message_data_t *msg)
{
char *user, *pass, *host, *port, *wild, *path, *s;
int oldform = 0;
struct wildmat *wmat = NULL, *w;
int len, err, n, feed = 1;
struct addrinfo hints, *res, *res0;
int sock = -1;
struct protstream *pin, *pout;
char buf[4096];
int body = 0, skip;
user = pass = host = port = wild = NULL;
if ((wild = strrchr(peer, '/')))
*wild++ = '\0';
else if ((wild = strrchr(peer, ':')) &&
strcspn(wild, "!*?,.") != strlen(wild)) {
*wild++ = '\0';
host = peer;
oldform = 1;
}
if (!oldform) {
if ((host = strchr(peer, '@'))) {
*host++ = '\0';
user = peer;
if ((pass = strchr(user, ':'))) *pass++ = '\0';
}
else
host = peer;
if ((port = strchr(host, ':'))) *port++ = '\0';
}
len = strlen(host);
path = msg->path;
while (path && (s = strchr(path, '!'))) {
if ((s - path) == len && !strncmp(path, host, len)) {
return;
}
path = s + 1;
}
if (wild && *wild) {
wmat = split_wildmats(wild);
feed = 0;
for (n = 0; n < msg->rcpt_num; n++) {
w = wmat;
while (w->pat &&
wildmat(msg->rcpt[n], w->pat) != 1) {
w++;
}
if (w->pat) {
if (!w->not) {
feed = 1;
}
else if (w->not < 0) {
feed = 0;
break;
}
else {
}
}
else {
}
}
free_wildmats(wmat);
}
if (!feed) return;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = 0;
if (!port || !*port) port = "119";
if ((err = getaddrinfo(host, port, &hints, &res0)) != 0) {
syslog(LOG_ERR, "getaddrinfo(%s, %s) failed: %m", host, port);
return;
}
for (res = res0; res; res = res->ai_next) {
if ((sock = socket(res->ai_family, res->ai_socktype,
res->ai_protocol)) < 0)
continue;
if (connect(sock, res->ai_addr, res->ai_addrlen) >= 0)
break;
close(sock);
sock = -1;
}
freeaddrinfo(res0);
if(sock < 0) {
syslog(LOG_ERR, "connect(%s:%s) failed: %m", host, port);
return;
}
pin = prot_new(sock, 0);
pout = prot_new(sock, 1);
prot_setflushonread(pin, pout);
if (!prot_fgets(buf, sizeof(buf), pin) || strncmp("200", buf, 3)) {
syslog(LOG_ERR, "peer doesn't allow posting");
goto quit;
}
if (user) {
prot_printf(pout, "MODE READER\r\n");
prot_fgets(buf, sizeof(buf), pin);
if (*user) {
prot_printf(pout, "AUTHINFO USER %s\r\n", user);
if (!prot_fgets(buf, sizeof(buf), pin)) {
syslog(LOG_ERR, "AUTHINFO USER terminated abnormally");
goto quit;
}
else if (!strncmp("381", buf, 3)) {
if (!pass) {
syslog(LOG_ERR, "need password for AUTHINFO PASS");
goto quit;
}
prot_printf(pout, "AUTHINFO PASS %s\r\n", pass);
if (!prot_fgets(buf, sizeof(buf), pin)) {
syslog(LOG_ERR, "AUTHINFO PASS terminated abnormally");
goto quit;
}
}
if (strncmp("281", buf, 3)) {
syslog(LOG_ERR, "authentication failed");
goto quit;
}
}
prot_printf(pout, "POST\r\n");
prot_flush(pout);
if (!prot_fgets(buf, sizeof(buf), pin) || strncmp("340", buf, 3)) {
syslog(LOG_ERR, "peer doesn't allow posting");
goto quit;
}
}
else {
prot_printf(pout, "IHAVE %s\r\n", msg->id);
prot_flush(pout);
if (!prot_fgets(buf, sizeof(buf), pin) || strncmp("335", buf, 3)) {
syslog(LOG_ERR, "peer doesn't want article %s", msg->id);
goto quit;
}
}
rewind(msg->f);
while (fgets(buf, sizeof(buf), msg->f)) {
if (!body && buf[0] == '\r' && buf[1] == '\n') {
body = 1;
}
skip = 0;
if (!body) {
if (!strncasecmp(buf, "Reply-To:", 9)) {
if (!strip_post_addresses(buf+9)) skip = 1;
}
}
if (!skip && buf[0] == '.') prot_putc('.', pout);
do {
if (!skip) prot_printf(pout, "%s", buf);
} while (buf[strlen(buf)-1] != '\n' &&
fgets(buf, sizeof(buf), msg->f));
}
if (buf[strlen(buf)-1] != '\n') prot_printf(pout, "\r\n");
prot_printf(pout, ".\r\n");
if (!prot_fgets(buf, sizeof(buf), pin) || strncmp("2", buf, 1)) {
syslog(LOG_ERR, "article %s transfer to peer failed", msg->id);
}
quit:
prot_printf(pout, "QUIT\r\n");
prot_flush(pout);
prot_fgets(buf, sizeof(buf), pin);
prot_NONBLOCK(pin);
prot_fill(pin);
close(sock);
prot_free(pin);
prot_free(pout);
return;
}
void printstring(const char *s __attribute__((unused)))
{
fatal("printstring() executed, but its not used for nntpd!",
EC_SOFTWARE);
}
#define ALLOC_SIZE 10
static void news2mail(message_data_t *msg)
{
struct annotation_data attrib;
int n, i, r;
FILE *sm;
static const char **smbuf = NULL;
static int allocsize = 0;
int sm_stat;
pid_t sm_pid;
char buf[4096], to[1024] = "";
if (!smbuf) {
allocsize += ALLOC_SIZE;
smbuf = xzmalloc(allocsize * sizeof(const char *));
smbuf[0] = "sendmail";
smbuf[1] = "-i";
smbuf[2] = "-f";
smbuf[3] = "<>";
smbuf[4] = "--";
}
for (i = 5, n = 0; n < msg->rcpt_num; n++) {
r = annotatemore_lookup(msg->rcpt[n],
"/vendor/cmu/cyrus-imapd/news2mail", "",
&attrib);
if (r) continue;
if (attrib.value) {
if (i >= allocsize - 1) {
allocsize += ALLOC_SIZE;
smbuf = xrealloc(smbuf, allocsize * sizeof(const char *));
}
smbuf[i++] = xstrdup(attrib.value);
smbuf[i] = NULL;
if (to[0]) strlcat(to, ", ", sizeof(to));
strlcat(to, attrib.value, sizeof(to));
}
}
if (i > 5) {
sm_pid = open_sendmail(smbuf, &sm);
if (!sm)
syslog(LOG_ERR, "news2mail: could not spawn sendmail process");
else {
int body = 0, skip, found_to = 0;
rewind(msg->f);
while (fgets(buf, sizeof(buf), msg->f)) {
if (!body && buf[0] == '\r' && buf[1] == '\n') {
body = 1;
if (!found_to) fprintf(sm, "To: %s\r\n", to);
}
skip = 0;
if (!body) {
if (!strncasecmp(buf, "Newsgroups:", 11)) {
fprintf(sm, "X-");
} else if (!strncasecmp(buf, "Xref:", 5) ||
!strncasecmp(buf, "Path:", 5) ||
!strncasecmp(buf, "NNTP-Posting-", 13)) {
skip = 1;
} else if (!strncasecmp(buf, "To:", 3)) {
fprintf(sm, "To: %s,\r\n", to);
memset(buf, ' ', 3);
found_to = 1;
} else if (!strncasecmp(buf, "Reply-To:", 9)) {
if (!strip_post_addresses(buf+9)) skip = 1;
}
}
do {
if (!skip) fprintf(sm, "%s", buf);
} while (buf[strlen(buf)-1] != '\n' &&
fgets(buf, sizeof(buf), msg->f));
}
if (buf[strlen(buf)-1] != '\n') fprintf(sm, "\r\n");
fclose(sm);
while (waitpid(sm_pid, &sm_stat, 0) < 0);
if (sm_stat)
syslog(LOG_ERR, "news2mail failed: %s",
sendmail_errstr(sm_stat));
}
for (i = 5; smbuf[i]; i++) {
free((char *) smbuf[i]);
smbuf[i] = NULL;
}
}
return;
}
static void cmd_post(char *msgid, int mode)
{
FILE *f = NULL;
message_data_t *msg;
int r = 0;
if (msgid && find_msgid(msgid, NULL, NULL)) {
r = NNTP_DONT_SEND;
}
if (mode != POST_TAKETHIS) {
if (r) {
prot_printf(nntp_out, "%u %s Do not send article\r\n",
post_codes[mode].no, msgid ? msgid : "");
return;
}
else {
prot_printf(nntp_out, "%u %s Send article\r\n",
post_codes[mode].cont, msgid ? msgid : "");
if (mode == POST_CHECK) return;
}
}
if (!r) {
f = tmpfile();
if (!f) r = IMAP_IOERROR;
}
if (f) {
msg_new(&msg);
r = savemsg(msg, f);
if (!r) r = deliver(msg);
if (!r) {
prot_printf(nntp_out, "%u %s Article received ok\r\n",
post_codes[mode].ok, msg->id ? msg->id : "");
if (msg->control && !config_mupdate_server) {
int r1 = 0;
if (!strncmp(msg->control, "newgroup", 8))
r1 = newgroup(msg);
else if (!strncmp(msg->control, "rmgroup", 7))
r1 = rmgroup(msg);
else if (!strncmp(msg->control, "mvgroup", 7))
r1 = mvgroup(msg);
else if (!strncmp(msg->control, "cancel", 6))
r1 = cancel(msg);
else
r1 = NNTP_UNKNOWN_CONTROLMSG;
if (r1)
syslog(LOG_WARNING, "control message '%s' failed: %s",
msg->control, error_message(r1));
else {
syslog(LOG_INFO, "control message '%s' succeeded",
msg->control);
}
}
if (msg->id) {
const char *peers = config_getstring(IMAPOPT_NEWSPEER);
if (peers) {
char *tmpbuf, *cur_peer, *next_peer;
cur_peer = tmpbuf = xstrdup(peers);
while (cur_peer) {
while (isspace(*cur_peer)) cur_peer++;
if ((next_peer = strchr(cur_peer, ' ')) ||
(next_peer = strchr(cur_peer, '\t')))
*next_peer++ = '\0';
feedpeer(cur_peer, msg);
cur_peer = next_peer;
}
free(tmpbuf);
}
news2mail(msg);
}
}
msg_free(msg);
if (stage) append_removestage(stage);
stage = NULL;
}
else {
spool_copy_msg(nntp_in, NULL);
}
if (r) {
prot_printf(nntp_out, "%u %s Failed receiving article (%s)\r\n",
post_codes[mode].fail, msgid ? msgid : "",
error_message(r));
}
prot_flush(nntp_out);
}
#ifdef HAVE_SSL
static void cmd_starttls(int nntps)
{
int result;
int *layerp;
sasl_ssf_t ssf;
char *auth_id;
if (nntp_starttls_done == 1) {
prot_printf(nntp_out, "502 %s\r\n",
"Already successfully executed STARTTLS");
return;
}
layerp = (int *) &ssf;
result=tls_init_serverengine("nntp",
5,
!nntps,
!nntps);
if (result == -1) {
syslog(LOG_ERR, "[nntpd] error initializing TLS");
if (nntps == 0)
prot_printf(nntp_out, "580 %s\r\n", "Error initializing TLS");
else
fatal("tls_init() failed",EC_TEMPFAIL);
return;
}
if (nntps == 0)
{
prot_printf(nntp_out, "382 %s\r\n", "Begin TLS negotiation now");
prot_flush(nntp_out);
}
result=tls_start_servertls(0,
1,
layerp,
&auth_id,
&tls_conn);
if (result==-1) {
if (nntps == 0) {
prot_printf(nntp_out, "580 Starttls failed\r\n");
syslog(LOG_NOTICE, "[nntpd] STARTTLS failed: %s", nntp_clienthost);
} else {
syslog(LOG_NOTICE, "nntps failed: %s", nntp_clienthost);
fatal("tls_start_servertls() failed", EC_TEMPFAIL);
}
return;
}
result = sasl_setprop(nntp_saslconn, SASL_SSF_EXTERNAL, &ssf);
if (result != SASL_OK) {
fatal("sasl_setprop() failed: cmd_starttls()", EC_TEMPFAIL);
}
saslprops.ssf = ssf;
result = sasl_setprop(nntp_saslconn, SASL_AUTH_EXTERNAL, auth_id);
if (result != SASL_OK) {
fatal("sasl_setprop() failed: cmd_starttls()", EC_TEMPFAIL);
}
if(saslprops.authid) {
free(saslprops.authid);
saslprops.authid = NULL;
}
if(auth_id)
saslprops.authid = xstrdup(auth_id);
prot_settls(nntp_in, tls_conn);
prot_settls(nntp_out, tls_conn);
nntp_starttls_done = 1;
}
#else
static void cmd_starttls(int nntps __attribute__((unused)))
{
fatal("cmd_starttls() called, but no OpenSSL", EC_SOFTWARE);
}
#endif
static struct wildmat *split_wildmats(char *str)
{
const char *prefix;
char pattern[MAX_MAILBOX_NAME+1] = "", *p, *c;
struct wildmat *wild = NULL;
int n = 0;
if ((prefix = config_getstring(IMAPOPT_NEWSPREFIX)))
snprintf(pattern, sizeof(pattern), "%s.", prefix);
p = pattern + strlen(pattern);
do {
if ((c = strrchr(str, ',')))
*c++ = '\0';
else
c = str;
if (!(n % 10))
wild = xrealloc(wild, (n + 11) * sizeof(struct wildmat));
if (*c == '!') wild[n].not = 1;
else if (*c == '@') wild[n].not = -1;
else wild[n].not = 0;
strcpy(p, wild[n].not ? c + 1 : c);
wild[n++].pat = xstrdup(pattern);
} while (c != str);
wild[n].pat = NULL;
return wild;
}
static void free_wildmats(struct wildmat *wild)
{
struct wildmat *w = wild;
while (w->pat) {
free(w->pat);
w++;
}
free(wild);
}