Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F120839536
nntpd.c
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
107 KB
Referenced Files
None
Subscribers
None
nntpd.c
View Options
/* nntpd.c -- NNTP server
*
* Copyright (c) 1994-2008 Carnegie Mellon University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The name "Carnegie Mellon University" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For permission or any legal
* details, please contact
* Carnegie Mellon University
* Center for Technology Transfer and Enterprise Creation
* 4615 Forbes Avenue
* Suite 302
* Pittsburgh, PA 15213
* (412) 268-7393, fax: (412) 268-7395
* innovation@andrew.cmu.edu
*
* 4. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by Computing Services
* at Carnegie Mellon University (http://www.cmu.edu/computing/)."
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* $Id: nntpd.c,v 1.83 2010/04/23 20:53:49 murch Exp $
*/
/*
* TODO:
*
* - add sender and PGP verification code for control messages
* - figure out what to do with control messages when proxying
*/
#include
<config.h>
#ifdef HAVE_UNISTD_H
#include
<unistd.h>
#endif
#ifdef HAVE_STDINT_H
#include
<stdint.h>
#endif
#include
<stdio.h>
#include
<errno.h>
#include
<string.h>
#include
<fcntl.h>
#include
<signal.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
"assert.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
"idle.h"
#include
"imap/imap_err.h"
#include
"index.h"
#include
"mailbox.h"
#include
"map.h"
#include
"mboxlist.h"
#include
"mkgmtime.h"
#include
"mupdate-client.h"
#include
"imap/nntp_err.h"
#include
"proc.h"
#include
"prot.h"
#include
"proxy.h"
#include
"retry.h"
#include
"times.h"
#include
"smtpclient.h"
#include
"spool.h"
#include
"sync_log.h"
#include
"telemetry.h"
#include
"tls.h"
#include
"userdeny.h"
#include
"util.h"
#include
"version.h"
#include
"wildmat.h"
#include
"xmalloc.h"
#include
"xstrlcat.h"
#include
"xstrlcpy.h"
extern
int
optind
;
extern
char
*
optarg
;
extern
int
opterr
;
/* PROXY STUFF */
/* we want a list of our outgoing connections here and which one we're
currently piping */
/* the current server most commands go to */
struct
backend
*
backend_current
=
NULL
;
/* our cached connections */
struct
backend
**
backend_cached
=
NULL
;
#ifdef HAVE_SSL
static
SSL
*
tls_conn
;
#endif
/* HAVE_SSL */
static
sasl_conn_t
*
nntp_saslconn
;
/* the sasl connection context */
static
int
nntp_timeout
;
static
char
newsprefix
[
100
]
=
""
;
static
struct
wildmat
*
newsgroups
=
NULL
;
static
char
*
nntp_userid
=
0
,
*
newsmaster
;
static
struct
auth_state
*
nntp_authstate
=
0
,
*
newsmaster_authstate
;
static
struct
index_state
*
group_state
;
static
const
char
*
nntp_clienthost
=
"[local]"
;
static
struct
protstream
*
nntp_out
=
NULL
;
static
struct
protstream
*
nntp_in
=
NULL
;
static
struct
protgroup
*
protin
=
NULL
;
static
int
nntp_logfd
=
-1
;
static
unsigned
nntp_exists
=
0
;
static
unsigned
nntp_current
=
0
;
static
unsigned
did_capabilities
=
0
;
static
int
allowanonymous
=
0
;
static
int
singleinstance
=
1
;
/* attempt single instance store */
static
struct
stagemsg
*
stage
=
NULL
;
/* Bitmasks for NNTP modes */
enum
{
MODE_READ
=
(
1
<<
0
),
MODE_FEED
=
(
1
<<
1
)
};
static
unsigned
nntp_capa
=
MODE_READ
|
MODE_FEED
;
/* general-purpose */
static
sasl_ssf_t
extprops_ssf
=
0
;
static
int
nntps
=
0
;
static
int
nntp_starttls_done
=
0
;
/* the sasl proxy policy context */
static
struct
proxy_context
nntp_proxyctx
=
{
0
,
1
,
&
nntp_authstate
,
NULL
,
NULL
};
/* for config.c */
const
int
config_need_data
=
CONFIG_NEED_PARTITION_DATA
;
/*
* values for article parts
* these correspond to the last digit of the response code
*/
enum
{
ARTICLE_ALL
=
0
,
ARTICLE_HEAD
=
1
,
ARTICLE_BODY
=
2
,
ARTICLE_STAT
=
3
};
/* values for post modes */
enum
{
POST_POST
=
0
,
POST_IHAVE
=
1
,
POST_CHECK
=
2
,
POST_TAKETHIS
=
3
};
/* response codes for each stage of posting */
static
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
getuserpass
(
struct
protstream
*
in
,
struct
buf
*
buf
);
static
int
parserange
(
char
*
str
,
uint32_t
*
uid
,
uint32_t
*
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
);
static
void
usage
(
void
);
void
shut_down
(
int
code
)
__attribute__
((
noreturn
));
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_cb_ft
*
)
&
mysasl_config
,
NULL
},
{
SASL_CB_PROXY_POLICY
,
(
mysasl_cb_ft
*
)
&
mysasl_proxy_policy
,
(
void
*
)
&
nntp_proxyctx
},
{
SASL_CB_CANON_USER
,
(
mysasl_cb_ft
*
)
&
mysasl_canon_user
,
NULL
},
{
SASL_CB_LIST_END
,
NULL
,
NULL
}
};
static
char
*
nntp_parsesuccess
(
char
*
str
,
const
char
**
status
)
{
char
*
success
=
NULL
;
if
(
!
strncmp
(
str
,
"283 "
,
4
))
{
success
=
str
+
4
;
}
if
(
status
)
*
status
=
NULL
;
return
success
;
}
static
struct
protocol_t
nntp_protocol
=
{
"nntp"
,
"nntp"
,
{
0
,
"20"
},
{
"CAPABILITIES"
,
NULL
,
"."
,
NULL
,
CAPAF_ONE_PER_LINE
,
{
{
"SASL "
,
CAPA_AUTH
},
{
"STARTTLS"
,
CAPA_STARTTLS
},
{
NULL
,
0
}
}
},
{
"STARTTLS"
,
"382"
,
"580"
,
0
},
{
"AUTHINFO SASL"
,
512
,
0
,
"28"
,
"48"
,
"383 "
,
"*"
,
&
nntp_parsesuccess
,
0
},
{
NULL
,
NULL
,
NULL
},
{
"DATE"
,
NULL
,
"111"
},
{
"QUIT"
,
NULL
,
"205"
}
};
/* proxy mboxlist_lookup; on misses, it asks the listener for this
machine to make a roundtrip to the master mailbox server to make
sure it's up to date */
static
int
mlookup
(
const
char
*
name
,
struct
mboxlist_entry
**
mbentryptr
)
{
struct
mboxlist_entry
*
mbentry
=
NULL
;
int
r
;
r
=
mboxlist_lookup
(
name
,
&
mbentry
,
NULL
);
if
(
r
==
IMAP_MAILBOX_NONEXISTENT
&&
config_mupdate_server
)
{
kick_mupdate
();
mboxlist_entry_free
(
&
mbentry
);
r
=
mboxlist_lookup
(
name
,
&
mbentry
,
NULL
);
}
if
(
r
)
return
r
;
if
(
mbentry
->
mbtype
&
MBTYPE_RESERVE
)
r
=
IMAP_MAILBOX_RESERVED
;
if
(
mbentry
->
mbtype
&
MBTYPE_MOVING
)
r
=
IMAP_MAILBOX_MOVED
;
if
(
mbentry
->
mbtype
&
MBTYPE_DELETED
)
r
=
IMAP_MAILBOX_NONEXISTENT
;
if
(
mbentryptr
&&
!
r
)
*
mbentryptr
=
mbentry
;
else
mboxlist_entry_free
(
&
mbentry
);
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
))
{
/* uh oh */
if
(
s
==
backend_current
&&
!
force_notfatal
)
fatal
(
"Lost connection to selected backend"
,
EC_UNAVAILABLE
);
proxy_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
))
{
/* uh oh */
if
(
s
==
backend_current
&&
!
force_notfatal
)
fatal
(
"Lost connection to selected backend"
,
EC_UNAVAILABLE
);
proxy_downserver
(
s
);
return
IMAP_SERVER_UNAVAILABLE
;
}
prot_printf
(
nntp_out
,
"%s"
,
buf
);
}
while
(
strcmp
(
buf
,
".
\r\n
"
));
return
0
;
}
/* end proxy support functions */
static
void
nntp_reset
(
void
)
{
int
i
;
proc_cleanup
();
/* close local mailbox */
if
(
group_state
)
index_close
(
&
group_state
);
/* close backend connections */
i
=
0
;
while
(
backend_cached
&&
backend_cached
[
i
])
{
proxy_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
;
if
(
protin
)
protgroup_reset
(
protin
);
#ifdef HAVE_SSL
if
(
tls_conn
)
{
tls_reset_servertls
(
&
tls_conn
);
tls_conn
=
NULL
;
}
#endif
cyrus_reset_stdio
();
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
;
}
/*
* run once when process is forked;
* MUST NOT exit directly; must return with non-zero error code
*/
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
);
/* set signal handlers */
signals_set_shutdown
(
&
shut_down
);
signal
(
SIGPIPE
,
SIG_IGN
);
/* load the SASL plugins */
global_sasl_init
(
1
,
1
,
mysasl_cb
);
if
((
prefix
=
config_getstring
(
IMAPOPT_NEWSPREFIX
)))
snprintf
(
newsprefix
,
sizeof
(
newsprefix
),
"%s."
,
prefix
);
newsgroups
=
split_wildmats
((
char
*
)
config_getstring
(
IMAPOPT_NEWSGROUPS
));
/* initialize duplicate delivery database */
if
(
duplicate_init
(
NULL
)
!=
0
)
{
syslog
(
LOG_ERR
,
"unable to init duplicate delivery database
\n
"
);
fatal
(
"unable to init duplicate delivery database"
,
EC_SOFTWARE
);
}
/* open the mboxlist, we'll need it for real work */
mboxlist_init
(
0
);
mboxlist_open
(
NULL
);
/* open the quota db, we'll need it for expunge */
quotadb_init
(
0
);
quotadb_open
(
NULL
);
/* open the user deny db */
denydb_init
(
0
);
denydb_open
(
0
);
/* setup for sending IMAP IDLE notifications */
idle_init
();
while
((
opt
=
getopt
(
argc
,
argv
,
"srfp:"
))
!=
EOF
)
{
switch
(
opt
)
{
case
's'
:
/* nntps (do starttls right away) */
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'
:
/* enter reader-only mode */
nntp_capa
=
MODE_READ
;
break
;
case
'f'
:
/* enter feeder-only mode */
nntp_capa
=
MODE_FEED
;
break
;
case
'p'
:
/* external protection */
extprops_ssf
=
atoi
(
optarg
);
break
;
default
:
usage
();
}
}
/* Initialize the annotatemore extention */
annotate_init
(
NULL
,
NULL
);
annotatemore_open
();
newsmaster
=
(
char
*
)
config_getstring
(
IMAPOPT_NEWSMASTER
);
newsmaster_authstate
=
auth_newstate
(
newsmaster
);
singleinstance
=
config_getswitch
(
IMAPOPT_SINGLEINSTANCESTORE
);
/* Create a protgroup for input from the client and selected backend */
protin
=
protgroup_new
(
2
);
return
0
;
}
/*
* run for each accepted connection
*/
int
service_main
(
int
argc
__attribute__
((
unused
)),
char
**
argv
__attribute__
((
unused
)),
char
**
envp
__attribute__
((
unused
)))
{
const
char
*
localip
,
*
remoteip
;
sasl_security_properties_t
*
secprops
=
NULL
;
int
shutdown
;
char
unavail
[
1024
];
signals_poll
();
sync_log_init
();
nntp_in
=
prot_new
(
0
,
0
);
nntp_out
=
prot_new
(
1
,
1
);
protgroup_insert
(
protin
,
nntp_in
);
/* Find out name of client host */
nntp_clienthost
=
get_clienthost
(
0
,
&
localip
,
&
remoteip
);
/* other params should be filled in */
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
);
/* will always return something valid */
secprops
=
mysasl_secprops
(
0
);
sasl_setprop
(
nntp_saslconn
,
SASL_SEC_PROPS
,
secprops
);
sasl_setprop
(
nntp_saslconn
,
SASL_SSF_EXTERNAL
,
&
extprops_ssf
);
if
(
localip
)
{
sasl_setprop
(
nntp_saslconn
,
SASL_IPLOCALPORT
,
localip
);
saslprops
.
iplocalport
=
xstrdup
(
localip
);
}
if
(
remoteip
)
{
char
hbuf
[
NI_MAXHOST
],
*
p
;
sasl_setprop
(
nntp_saslconn
,
SASL_IPREMOTEPORT
,
remoteip
);
saslprops
.
ipremoteport
=
xstrdup
(
remoteip
);
/* Create pre-authentication telemetry log based on client IP */
strlcpy
(
hbuf
,
remoteip
,
NI_MAXHOST
);
if
((
p
=
strchr
(
hbuf
,
';'
)))
*
p
=
'\0'
;
nntp_logfd
=
telemetry_log
(
hbuf
,
nntp_in
,
nntp_out
,
0
);
}
proc_register
(
"nntpd"
,
nntp_clienthost
,
NULL
,
NULL
);
/* Set inactivity timer */
nntp_timeout
=
config_getint
(
IMAPOPT_NNTPTIMEOUT
);
if
(
nntp_timeout
<
3
)
nntp_timeout
=
3
;
nntp_timeout
*=
60
;
prot_settimeout
(
nntp_in
,
nntp_timeout
);
prot_setflushonread
(
nntp_in
,
nntp_out
);
/* we were connected on nntps port so we should do
TLS negotiation immediatly */
if
(
nntps
==
1
)
cmd_starttls
(
1
);
if
((
shutdown
=
shutdown_file
(
unavail
,
sizeof
(
unavail
))))
{
prot_printf
(
nntp_out
,
"%u"
,
400
);
}
else
{
prot_printf
(
nntp_out
,
"%u"
,
(
nntp_capa
&
MODE_READ
)
?
200
:
201
);
}
if
(
config_serverinfo
)
prot_printf
(
nntp_out
,
" %s"
,
config_servername
);
if
(
config_serverinfo
==
IMAP_ENUM_SERVERINFO_ON
)
{
prot_printf
(
nntp_out
,
" Cyrus NNTP%s %s"
,
config_mupdate_server
?
" Murder"
:
""
,
cyrus_version
());
}
if
(
shutdown
)
{
prot_printf
(
nntp_out
,
"server unavailable, %s
\r\n
"
,
unavail
);
shut_down
(
0
);
}
else
{
prot_printf
(
nntp_out
,
" server ready, posting %s
\r\n
"
,
(
nntp_capa
&
MODE_READ
)
?
"allowed"
:
"prohibited"
);
}
cmdloop
();
/* QUIT executed */
/* cleanup */
nntp_reset
();
return
0
;
}
/* Called by service API to shut down the service */
void
service_abort
(
int
error
)
{
shut_down
(
error
);
}
static
void
usage
(
void
)
{
prot_printf
(
nntp_out
,
"503 usage: nntpd [-C <alt_config>] [-s]
\r\n
"
);
prot_flush
(
nntp_out
);
exit
(
EC_USAGE
);
}
/*
* Cleanly shut down and exit
*/
void
shut_down
(
int
code
)
{
int
i
;
in_shutdown
=
1
;
proc_cleanup
();
/* close local mailbox */
if
(
group_state
)
index_close
(
&
group_state
);
/* close backend connections */
i
=
0
;
while
(
backend_cached
&&
backend_cached
[
i
])
{
proxy_downserver
(
backend_cached
[
i
]);
free
(
backend_cached
[
i
]
->
context
);
free
(
backend_cached
[
i
]);
i
++
;
}
if
(
backend_cached
)
free
(
backend_cached
);
sync_log_done
();
duplicate_done
();
mboxlist_close
();
mboxlist_done
();
quotadb_close
();
quotadb_done
();
denydb_close
();
denydb_done
();
annotatemore_close
();
annotate_done
();
idle_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
);
}
if
(
protin
)
protgroup_free
(
protin
);
#ifdef HAVE_SSL
tls_shutdown_serverengine
();
#endif
if
(
newsgroups
)
free_wildmats
(
newsgroups
);
cyrus_done
();
exit
(
code
);
}
EXPORTED
void
fatal
(
const
char
*
s
,
int
code
)
{
static
int
recurse_code
=
0
;
if
(
recurse_code
)
{
/* We were called recursively. Just give up */
proc_cleanup
();
exit
(
recurse_code
);
}
recurse_code
=
code
;
if
(
nntp_out
)
{
prot_printf
(
nntp_out
,
"400 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
);
}
/* Reset the given sasl_conn_t to a sane state */
static
int
reset_saslconn
(
sasl_conn_t
**
conn
)
{
int
ret
;
sasl_security_properties_t
*
secprops
=
NULL
;
sasl_dispose
(
conn
);
/* do initialization typical of service_main */
ret
=
sasl_server_new
(
"nntp"
,
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
(
0
);
ret
=
sasl_setprop
(
*
conn
,
SASL_SEC_PROPS
,
secprops
);
if
(
ret
!=
SASL_OK
)
return
ret
;
/* end of service_main initialization excepting SSF */
/* If we have TLS/SSL info, set it */
if
(
saslprops
.
ssf
)
{
ret
=
sasl_setprop
(
*
conn
,
SASL_SSF_EXTERNAL
,
&
saslprops
.
ssf
);
}
else
{
ret
=
sasl_setprop
(
*
conn
,
SASL_SSF_EXTERNAL
,
&
extprops_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
;
}
/* End TLS/SSL Info */
return
SASL_OK
;
}
/*
* Checks to make sure that the given mailbox is actually something
* that we're serving up as a newsgroup. Returns 1 if yes, 0 if no.
*/
static
int
is_newsgroup
(
const
char
*
mbox
)
{
struct
wildmat
*
wild
;
/* don't use personal mailboxes */
if
(
!
mbox
||
!*
mbox
||
(
!
strncasecmp
(
mbox
,
"INBOX"
,
5
)
&&
(
!
mbox
[
5
]
||
mbox
[
5
]
==
'.'
))
||
!
strncmp
(
mbox
,
"user."
,
5
)
||
strncmp
(
mbox
,
newsprefix
,
strlen
(
newsprefix
)))
return
0
;
/* check shared mailboxes against the 'newsgroups' wildmat */
wild
=
newsgroups
;
while
(
wild
->
pat
&&
wildmat
(
mbox
,
wild
->
pat
)
!=
1
)
wild
++
;
/* if we don't have a match, or its a negative match, don't use it */
if
(
!
wild
->
pat
||
wild
->
not
)
return
0
;
/* otherwise, its usable */
return
1
;
}
/*
* Top-level command loop parsing
*/
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
;
uint32_t
uid
,
last
;
struct
backend
*
be
;
char
curgroup
[
MAX_MAILBOX_BUFFER
]
=
""
;
allowanonymous
=
config_getswitch
(
IMAPOPT_ALLOWANONYMOUSLOGIN
);
for
(;;)
{
/* Flush any buffered output */
prot_flush
(
nntp_out
);
if
(
backend_current
)
prot_flush
(
backend_current
->
out
);
/* Check for shutdown file */
if
(
shutdown_file
(
buf
,
sizeof
(
buf
))
||
(
nntp_userid
&&
userdeny
(
nntp_userid
,
config_ident
,
buf
,
sizeof
(
buf
))))
{
prot_printf
(
nntp_out
,
"400 %s
\r\n
"
,
buf
);
shut_down
(
0
);
}
signals_poll
();
if
(
!
proxy_check_input
(
protin
,
nntp_in
,
nntp_out
,
backend_current
?
backend_current
->
in
:
NULL
,
NULL
,
0
))
{
/* No input from client */
continue
;
}
if
(
group_state
&&
config_getswitch
(
IMAPOPT_DISCONNECT_ON_VANISHED_MAILBOX
))
{
if
(
group_state
->
mailbox
->
i
.
options
&
OPT_MAILBOX_DELETED
)
{
/* Mailbox has been (re)moved */
syslog
(
LOG_WARNING
,
"Newsgroup %s has been (re)moved out from under client"
,
group_state
->
mboxname
);
prot_printf
(
nntp_out
,
"400 Newsgroup has been (re)moved
\r\n
"
);
shut_down
(
0
);
}
}
/* Parse command name */
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
(
!
cmd
.
s
[
0
])
{
prot_printf
(
nntp_out
,
"501 Empty command
\r\n
"
);
eatline
(
nntp_in
,
c
);
continue
;
}
if
(
Uislower
(
cmd
.
s
[
0
]))
cmd
.
s
[
0
]
=
toupper
((
unsigned
char
)
cmd
.
s
[
0
]);
for
(
p
=
&
cmd
.
s
[
1
];
*
p
;
p
++
)
{
if
(
Uisupper
(
*
p
))
*
p
=
tolower
((
unsigned
char
)
*
p
);
}
/* Ihave/Takethis only allowed for feeders */
if
(
!
(
nntp_capa
&
MODE_FEED
)
&&
strchr
(
"IT"
,
cmd
.
s
[
0
]))
goto
noperm
;
/* Body/Date/Group/Newgroups/Newnews/Next/Over/Post/Xhdr/Xover/Xpat
only allowed for readers */
if
(
!
(
nntp_capa
&
MODE_READ
)
&&
strchr
(
"BDGNOPX"
,
cmd
.
s
[
0
]))
goto
noperm
;
/* Only Authinfo/Capabilities/Check/Head/Help/Ihave/List Active/
Mode/Quit/Starttls/Stat/Takethis allowed when not logged in */
if
(
!
nntp_authstate
&&
!
allowanonymous
&&
!
strchr
(
"ACHILMQST"
,
cmd
.
s
[
0
]))
goto
nologin
;
/* In case a [LIST]GROUP fails or
a retrieval by msgid makes us switch groups */
strcpy
(
curgroup
,
group_state
?
group_state
->
mboxname
:
""
);
switch
(
cmd
.
s
[
0
])
{
case
'A'
:
if
(
!
strcmp
(
cmd
.
s
,
"Authinfo"
))
{
if
(
c
!=
' '
)
goto
missingargs
;
c
=
getword
(
nntp_in
,
&
arg1
);
/* subcommand */
if
(
c
==
EOF
)
goto
missingargs
;
lcase
(
arg1
.
s
);
if
(
!
strcmp
(
arg1
.
s
,
"user"
)
||
!
strcmp
(
arg1
.
s
,
"pass"
))
{
if
(
c
!=
' '
)
goto
missingargs
;
c
=
getuserpass
(
nntp_in
,
&
arg2
);
/* user/pass */
if
(
c
==
EOF
)
goto
missingargs
;
if
(
c
==
'\r'
)
c
=
prot_getc
(
nntp_in
);
if
(
c
!=
'\n'
)
goto
extraargs
;
if
(
arg1
.
s
[
0
]
==
'u'
)
cmd_authinfo_user
(
arg2
.
s
);
else
cmd_authinfo_pass
(
arg2
.
s
);
}
else
if
(
!
strcmp
(
arg1
.
s
,
"sasl"
)
||
!
strcmp
(
arg1
.
s
,
"generic"
))
{
arg2
.
len
=
arg3
.
len
=
0
;
/* mech name required for SASL but not GENERIC */
if
((
arg1
.
s
[
0
]
==
's'
)
&&
(
c
!=
' '
))
goto
missingargs
;
if
(
c
==
' '
)
{
c
=
getword
(
nntp_in
,
&
arg2
);
/* mech name */
if
(
c
==
EOF
)
goto
missingargs
;
if
(
c
==
' '
)
{
c
=
getword
(
nntp_in
,
&
arg3
);
/* init response */
if
(
c
==
EOF
)
goto
missingargs
;
}
}
if
(
c
==
'\r'
)
c
=
prot_getc
(
nntp_in
);
if
(
c
!=
'\n'
)
goto
extraargs
;
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_authstate
&&
!
allowanonymous
)
goto
nologin
;
else
if
(
!
strcmp
(
cmd
.
s
,
"Article"
))
{
char
*
msgid
;
mode
=
ARTICLE_ALL
;
article
:
if
(
arg1
.
s
)
*
arg1
.
s
=
0
;
if
(
c
==
' '
)
{
c
=
getword
(
nntp_in
,
&
arg1
);
/* number/msgid (optional) */
if
(
c
==
EOF
)
goto
missingargs
;
}
if
(
c
==
'\r'
)
c
=
prot_getc
(
nntp_in
);
if
(
c
!=
'\n'
)
goto
extraargs
;
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
);
if
(
be
!=
backend_current
)
{
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
(
msgid
)
goto
prevgroup
;
}
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
);
/* keyword (optional) */
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"
))
{
arg2
.
len
=
0
;
/* GROUP command (no range) */
group
:
#define LISTGROUP (arg2.len)
if
(
!
LISTGROUP
&&
c
!=
' '
)
goto
missingargs
;
if
(
c
==
' '
)
{
c
=
getword
(
nntp_in
,
&
arg1
);
/* group */
if
(
c
==
EOF
)
goto
missingargs
;
}
if
(
LISTGROUP
&&
c
==
' '
)
{
c
=
getword
(
nntp_in
,
&
arg2
);
/* range (optional) */
if
(
c
==
EOF
)
goto
missingargs
;
}
if
(
c
==
'\r'
)
c
=
prot_getc
(
nntp_in
);
if
(
c
!=
'\n'
)
goto
extraargs
;
be
=
backend_current
;
if
(
arg1
.
len
&&
(
!
is_newsgroup
(
arg1
.
s
)
||
(
r
=
open_group
(
arg1
.
s
,
0
,
&
be
,
NULL
))))
goto
nogroup
;
else
if
(
be
)
{
prot_printf
(
be
->
out
,
"%s"
,
cmd
.
s
);
if
(
arg1
.
len
)
{
prot_printf
(
be
->
out
,
" %s"
,
arg1
.
s
);
if
(
LISTGROUP
)
prot_printf
(
be
->
out
,
" %s"
,
arg2
.
s
);
}
prot_printf
(
be
->
out
,
"
\r\n
"
);
r
=
read_response
(
be
,
0
,
&
result
);
if
(
r
)
goto
nogroup
;
prot_printf
(
nntp_out
,
"%s"
,
result
);
if
(
!
strncmp
(
result
,
"211"
,
3
))
{
if
(
LISTGROUP
)
pipe_to_end_of_response
(
be
,
0
);
if
(
backend_current
&&
backend_current
!=
be
)
{
/* remove backend_current from the protgroup */
protgroup_delete
(
protin
,
backend_current
->
in
);
}
backend_current
=
be
;
/* add backend_current to the protgroup */
protgroup_insert
(
protin
,
backend_current
->
in
);
}
}
else
if
(
!
group_state
)
goto
noopengroup
;
else
if
(
LISTGROUP
&&
parserange
(
arg2
.
s
,
&
uid
,
&
last
,
NULL
,
NULL
)
!=
0
)
{
/* parserange() will handle error code -- do nothing */
}
else
{
if
(
backend_current
)
{
/* remove backend_current from the protgroup */
protgroup_delete
(
protin
,
backend_current
->
in
);
}
backend_current
=
NULL
;
nntp_exists
=
group_state
->
exists
;
nntp_current
=
nntp_exists
>
0
;
prot_printf
(
nntp_out
,
"211 %u %lu %lu %s
\r\n
"
,
nntp_exists
,
nntp_exists
?
index_getuid
(
group_state
,
1
)
:
group_state
->
last_uid
+
1
,
nntp_exists
?
index_getuid
(
group_state
,
nntp_exists
)
:
group_state
->
last_uid
,
group_state
->
mboxname
+
strlen
(
newsprefix
));
if
(
LISTGROUP
)
{
int
msgno
,
last_msgno
;
msgno
=
index_finduid
(
group_state
,
uid
);
if
(
!
msgno
||
index_getuid
(
group_state
,
msgno
)
!=
uid
)
{
msgno
++
;
}
last_msgno
=
index_finduid
(
group_state
,
last
);
for
(;
msgno
<=
last_msgno
;
msgno
++
)
{
prot_printf
(
nntp_out
,
"%u
\r\n
"
,
index_getuid
(
group_state
,
msgno
));
}
prot_printf
(
nntp_out
,
".
\r\n
"
);
}
}
#undef LISTGROUP
}
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_authstate
&&
!
allowanonymous
)
goto
nologin
;
else
if
(
!
strcmp
(
cmd
.
s
,
"Hdr"
))
{
char
*
msgid
;
hdr
:
if
(
arg2
.
s
)
*
arg2
.
s
=
0
;
if
(
c
!=
' '
)
goto
missingargs
;
c
=
getword
(
nntp_in
,
&
arg1
);
/* header */
if
(
c
==
EOF
)
goto
missingargs
;
if
(
c
==
' '
)
{
c
=
getword
(
nntp_in
,
&
arg2
);
/* range (optional) */
if
(
c
==
EOF
)
goto
missingargs
;
}
if
(
c
==
'\r'
)
c
=
prot_getc
(
nntp_in
);
if
(
c
!=
'\n'
)
goto
extraargs
;
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
);
if
(
be
!=
backend_current
)
{
r
=
read_response
(
be
,
0
,
&
result
);
if
(
r
)
goto
noopengroup
;
prot_printf
(
nntp_out
,
"%s"
,
result
);
if
(
!
strncmp
(
result
,
"22"
,
2
))
{
/* 221 or 225 */
pipe_to_end_of_response
(
be
,
0
);
}
}
}
else
cmd_hdr
(
cmd
.
s
,
arg1
.
s
,
NULL
,
msgid
,
uid
,
last
);
}
if
(
msgid
)
goto
prevgroup
;
}
else
goto
badcmd
;
break
;
case
'I'
:
if
(
!
strcmp
(
cmd
.
s
,
"Ihave"
))
{
mode
=
POST_IHAVE
;
ihave
:
if
(
c
!=
' '
)
goto
missingargs
;
c
=
getword
(
nntp_in
,
&
arg1
);
/* msgid */
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
);
/* subcommand (optional) */
if
(
c
==
EOF
)
goto
missingargs
;
if
(
c
==
' '
)
{
c
=
getword
(
nntp_in
,
&
arg2
);
/* argument (optional) */
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
);
goto
prevgroup
;
/* In case we did LIST [ACTIVE] */
}
else
if
(
!
(
nntp_capa
&
MODE_READ
))
goto
noperm
;
else
if
(
!
nntp_authstate
&&
!
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
"
);
}
else
if
(
!
group_state
)
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
(
group_state
,
--
nntp_current
);
prot_printf
(
nntp_out
,
"223 %u %s
\r\n
"
,
index_getuid
(
group_state
,
nntp_current
),
msgid
?
msgid
:
"<0>"
);
if
(
msgid
)
free
(
msgid
);
}
}
else
if
(
!
strcmp
(
cmd
.
s
,
"Listgroup"
))
{
arg1
.
len
=
0
;
/* group is optional */
buf_setcstr
(
&
arg2
,
"1-"
);
/* default range is all */
buf_cstring
(
&
arg2
);
/* appends a '\0' */
goto
group
;
}
else
goto
badcmd
;
break
;
case
'M'
:
if
(
!
strcmp
(
cmd
.
s
,
"Mode"
))
{
if
(
c
!=
' '
)
goto
missingargs
;
c
=
getword
(
nntp_in
,
&
arg1
);
/* mode */
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
);
/* date */
if
(
c
!=
' '
)
goto
missingargs
;
c
=
getword
(
nntp_in
,
&
arg2
);
/* time */
if
(
c
==
EOF
)
goto
missingargs
;
if
(
c
==
' '
)
{
c
=
getword
(
nntp_in
,
&
arg3
);
/* "GMT" (optional) */
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
);
/* wildmat */
if
(
c
!=
' '
)
goto
missingargs
;
c
=
getword
(
nntp_in
,
&
arg2
);
/* date */
if
(
c
!=
' '
)
goto
missingargs
;
c
=
getword
(
nntp_in
,
&
arg3
);
/* time */
if
(
c
==
EOF
)
goto
missingargs
;
if
(
c
==
' '
)
{
c
=
getword
(
nntp_in
,
&
arg4
);
/* "GMT" (optional) */
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
"
);
}
else
if
(
!
group_state
)
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
(
group_state
,
++
nntp_current
);
prot_printf
(
nntp_out
,
"223 %u %s
\r\n
"
,
index_getuid
(
group_state
,
nntp_current
),
msgid
?
msgid
:
"<0>"
);
if
(
msgid
)
free
(
msgid
);
}
}
else
goto
badcmd
;
break
;
case
'O'
:
if
(
!
strcmp
(
cmd
.
s
,
"Over"
))
{
char
*
msgid
;
over
:
if
(
arg1
.
s
)
*
arg1
.
s
=
0
;
if
(
c
==
' '
)
{
c
=
getword
(
nntp_in
,
&
arg1
);
/* range/msgid (optional) */
if
(
c
==
EOF
)
goto
missingargs
;
}
if
(
c
==
'\r'
)
c
=
prot_getc
(
nntp_in
);
if
(
c
!=
'\n'
)
goto
extraargs
;
msgid
=
NULL
;
if
(
parserange
(
arg1
.
s
,
&
uid
,
&
last
,
/* XOVER doesn't accept message-id */
(
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
);
if
(
be
!=
backend_current
)
{
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
(
msgid
)
goto
prevgroup
;
}
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
;
/* XXX discard any input pipelined after STARTTLS */
prot_flush
(
nntp_in
);
cmd_starttls
(
0
);
}
else
if
(
!
strcmp
(
cmd
.
s
,
"Stat"
))
{
mode
=
ARTICLE_STAT
;
goto
article
;
}
else
if
(
!
nntp_authstate
&&
!
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
*
msgid
;
if
(
c
!=
' '
)
goto
missingargs
;
c
=
getword
(
nntp_in
,
&
arg1
);
/* header */
if
(
c
!=
' '
)
goto
missingargs
;
/* gobble extra whitespace (hack for Mozilla) */
while
((
c
=
prot_getc
(
nntp_in
))
==
' '
);
prot_ungetc
(
c
,
nntp_in
);
c
=
getword
(
nntp_in
,
&
arg2
);
/* range */
if
(
c
!=
' '
)
goto
missingargs
;
c
=
getword
(
nntp_in
,
&
arg3
);
/* wildmat */
if
(
c
==
EOF
)
goto
missingargs
;
/* XXX per RFC 2980, we can have multiple patterns */
if
(
c
==
'\r'
)
c
=
prot_getc
(
nntp_in
);
if
(
c
!=
'\n'
)
goto
extraargs
;
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
);
if
(
be
!=
backend_current
)
{
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
(
msgid
)
goto
prevgroup
;
}
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
));
prevgroup
:
/* Return to previously selected group */
if
(
*
curgroup
&&
(
!
group_state
||
strcmp
(
curgroup
,
group_state
->
mboxname
)))
{
open_group
(
curgroup
,
1
,
NULL
,
NULL
);
}
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
;
}
}
/*
* duplicate_find() callback function to fetch a message by msgid
*/
struct
findrock
{
const
char
*
mailbox
;
unsigned
long
uid
;
};
static
int
find_cb
(
const
duplicate_key_t
*
dkey
,
time_t
mark
__attribute__
((
unused
)),
unsigned
long
uid
,
void
*
rock
)
{
struct
findrock
*
frock
=
(
struct
findrock
*
)
rock
;
/* skip mailboxes that we don't serve as newsgroups */
if
(
!
is_newsgroup
(
dkey
->
to
))
return
0
;
frock
->
mailbox
=
dkey
->
to
;
frock
->
uid
=
uid
;
return
CYRUSDB_DONE
;
}
static
int
my_find_msgid
(
char
*
msgid
,
char
**
mailbox
,
uint32_t
*
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
;
}
/*
* Parse a username or password (token which may contain SP or TAB)
*/
#define MAX_NNTP_ARG 497
static
int
getuserpass
(
struct
protstream
*
in
,
struct
buf
*
buf
)
{
int
c
;
buf_reset
(
buf
);
for
(;;)
{
c
=
prot_getc
(
in
);
if
(
c
==
EOF
||
c
==
'\r'
||
c
==
'\n'
)
{
buf_cstring
(
buf
);
/* appends a '\0' */
return
c
;
}
buf_putc
(
buf
,
c
);
if
(
buf_len
(
buf
)
>
MAX_NNTP_ARG
)
{
fatal
(
"argument too long"
,
EC_IOERR
);
}
}
}
static
int
parserange
(
char
*
str
,
uint32_t
*
uid
,
uint32_t
*
last
,
char
**
msgid
,
struct
backend
**
ret
)
{
const
char
*
p
=
NULL
;
char
*
mboxname
;
int
r
=
0
;
*
uid
=
0
;
if
(
last
)
*
last
=
0
;
if
(
msgid
)
*
msgid
=
NULL
;
if
(
ret
)
*
ret
=
NULL
;
if
(
!
str
||
!*
str
)
{
/* no argument, use current article */
if
(
backend_current
)
{
if
(
ret
)
*
ret
=
backend_current
;
}
else
if
(
!
group_state
)
goto
noopengroup
;
else
if
(
!
nntp_current
)
goto
nocurrent
;
else
{
*
uid
=
index_getuid
(
group_state
,
nntp_current
);
if
(
last
)
*
last
=
*
uid
;
}
}
else
if
(
*
str
==
'<'
)
{
/* message-id, find server and/or mailbox */
if
(
!
msgid
)
goto
badrange
;
if
(
!
my_find_msgid
(
str
,
&
mboxname
,
uid
))
goto
nomsgid
;
*
msgid
=
str
;
/* open group if its different from our current one */
if
(
!
group_state
||
strcmp
(
mboxname
,
group_state
->
mboxname
))
{
if
((
r
=
open_group
(
mboxname
,
1
,
ret
,
NULL
)))
goto
nomsgid
;
}
}
else
if
(
backend_current
)
{
if
(
ret
)
*
ret
=
backend_current
;
}
else
if
(
!
group_state
)
goto
noopengroup
;
else
if
(
parseuint32
(
str
,
&
p
,
uid
)
||
uid
==
0
)
goto
badrange
;
else
if
(
p
&&
*
p
)
{
/* extra stuff, check for range */
if
(
!
last
||
(
*
p
!=
'-'
))
goto
badrange
;
if
(
*++
p
)
{
if
(
parseuint32
(
p
,
NULL
,
last
))
*
last
=
0
;
}
else
*
last
=
UINT32_MAX
;
/* open range -> use highest possible UID */
}
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
;
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, message number, 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)))
/*
* Parse a date/time specification per RFC3977 section 7.3.
*/
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
;
memset
(
&
tm
,
0
,
sizeof
(
tm
));
/* check format of strings */
if
((
datelen
!=
6
&&
datelen
!=
8
)
||
strlen
(
timestr
)
!=
6
||
(
gmt
&&
strcasecmp
(
gmt
,
"GMT"
)))
return
-1
;
/* convert datestr to ulong */
d
=
strtoul
(
datestr
,
&
p
,
10
);
if
(
d
==
ULONG_MAX
||
*
p
)
return
-1
;
/* convert timestr to ulong */
t
=
strtoul
(
timestr
,
&
p
,
10
);
if
(
t
==
ULONG_MAX
||
*
p
)
return
-1
;
/* populate the time struct */
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
;
/* massage the year to years since 1900 */
if
(
tm
.
tm_year
>
99
)
tm
.
tm_year
-=
1900
;
else
{
/*
* guess century
* if year > current year, use previous century
* otherwise, use current century
*/
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
;
}
/* sanity check the date/time (including leap day and leap second) */
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
/* used for LIST ACTIVE only */
)
{
char
mailboxname
[
MAX_MAILBOX_BUFFER
];
int
r
=
0
;
struct
mboxlist_entry
*
mbentry
=
NULL
;
struct
backend
*
backend_next
=
NULL
;
/* close local group */
if
(
group_state
)
index_close
(
&
group_state
);
if
(
!
has_prefix
)
{
snprintf
(
mailboxname
,
sizeof
(
mailboxname
),
"%s%s"
,
newsprefix
,
name
);
name
=
mailboxname
;
}
if
(
!
r
)
r
=
mlookup
(
name
,
&
mbentry
);
if
(
!
r
&&
mbentry
->
acl
)
{
int
myrights
=
cyrus_acl_myrights
(
nntp_authstate
,
mbentry
->
acl
);
if
(
postable
)
*
postable
=
myrights
&
ACL_POST
;
if
(
!
postable
&&
/* allow limited 'r' for LIST ACTIVE */
!
(
myrights
&
ACL_READ
))
{
r
=
(
myrights
&
ACL_LOOKUP
)
?
IMAP_PERMISSION_DENIED
:
IMAP_MAILBOX_NONEXISTENT
;
}
}
if
(
r
)
{
mboxlist_entry_free
(
&
mbentry
);
return
r
;
}
if
(
mbentry
->
server
)
{
/* remote group */
backend_next
=
proxy_findserver
(
mbentry
->
server
,
&
nntp_protocol
,
nntp_authstate
?
nntp_userid
:
"anonymous"
,
&
backend_cached
,
&
backend_current
,
NULL
,
nntp_in
);
mboxlist_entry_free
(
&
mbentry
);
if
(
!
backend_next
)
return
IMAP_SERVER_UNAVAILABLE
;
*
ret
=
backend_next
;
}
else
{
/* local group */
struct
index_init
init
;
mboxlist_entry_free
(
&
mbentry
);
memset
(
&
init
,
0
,
sizeof
(
struct
index_init
));
init
.
userid
=
nntp_authstate
?
nntp_userid
:
NULL
;
init
.
authstate
=
nntp_authstate
;
r
=
index_open
(
name
,
&
init
,
&
group_state
);
if
(
r
)
return
r
;
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
;
int
mechcount
=
0
;
prot_printf
(
nntp_out
,
"101 Capability list follows:
\r\n
"
);
prot_printf
(
nntp_out
,
"VERSION 2
\r\n
"
);
if
(
nntp_authstate
||
(
config_serverinfo
==
IMAP_ENUM_SERVERINFO_ON
))
{
prot_printf
(
nntp_out
,
"IMPLEMENTATION Cyrus NNTP%s %s
\r\n
"
,
config_mupdate_server
?
" Murder"
:
""
,
cyrus_version
());
}
/* add STARTTLS */
if
(
tls_enabled
()
&&
!
nntp_starttls_done
&&
!
nntp_authstate
)
prot_printf
(
nntp_out
,
"STARTTLS
\r\n
"
);
/* check for SASL mechs */
sasl_listmech
(
nntp_saslconn
,
NULL
,
"SASL "
,
" "
,
"
\r\n
"
,
&
mechlist
,
NULL
,
&
mechcount
);
/* add the AUTHINFO variants */
if
(
!
nntp_authstate
)
{
prot_printf
(
nntp_out
,
"AUTHINFO%s%s
\r\n
"
,
(
nntp_starttls_done
||
(
extprops_ssf
>
1
)
||
config_getswitch
(
IMAPOPT_ALLOWPLAINTEXT
))
?
" USER"
:
""
,
mechcount
?
" SASL"
:
""
);
}
/* add the SASL mechs */
if
(
mechcount
)
prot_printf
(
nntp_out
,
"%s"
,
mechlist
);
/* add the reader capabilities/extensions */
if
((
nntp_capa
&
MODE_READ
)
&&
(
nntp_authstate
||
allowanonymous
))
{
prot_printf
(
nntp_out
,
"READER
\r\n
"
);
prot_printf
(
nntp_out
,
"POST
\r\n
"
);
if
(
config_getswitch
(
IMAPOPT_ALLOWNEWNEWS
))
prot_printf
(
nntp_out
,
"NEWNEWS
\r\n
"
);
prot_printf
(
nntp_out
,
"HDR
\r\n
"
);
prot_printf
(
nntp_out
,
"OVER
\r\n
"
);
prot_printf
(
nntp_out
,
"XPAT
\r\n
"
);
}
/* add the feeder capabilities/extensions */
if
(
nntp_capa
&
MODE_FEED
)
{
prot_printf
(
nntp_out
,
"IHAVE
\r\n
"
);
prot_printf
(
nntp_out
,
"STREAMING
\r\n
"
);
}
/* add the LIST variants */
prot_printf
(
nntp_out
,
"LIST ACTIVE%s
\r\n
"
,
((
nntp_capa
&
MODE_READ
)
&&
(
nntp_authstate
||
allowanonymous
))
?
" HEADERS NEWSGROUPS OVERVIEW.FMT"
:
""
);
prot_printf
(
nntp_out
,
".
\r\n
"
);
did_capabilities
=
1
;
}
/*
* duplicate_find() callback function to build Xref content
*/
static
int
xref_cb
(
const
duplicate_key_t
*
dkey
,
time_t
mark
__attribute__
((
unused
)),
unsigned
long
uid
,
void
*
rock
)
{
struct
buf
*
buf
=
(
struct
buf
*
)
rock
;
/* skip mailboxes that we don't serve as newsgroups */
if
(
is_newsgroup
(
dkey
->
to
))
{
buf_printf
(
buf
,
" %s:%lu"
,
dkey
->
to
+
strlen
(
newsprefix
),
uid
);
}
return
0
;
}
/*
* Build an Xref header. We have to do this on the fly because there is
* no way to store it in the article at delivery time.
*/
static
void
build_xref
(
const
char
*
msgid
,
struct
buf
*
buf
,
int
body_only
)
{
if
(
!
body_only
)
buf_appendcstr
(
buf
,
"Xref: "
);
buf_appendcstr
(
buf
,
config_servername
);
duplicate_find
(
msgid
,
xref_cb
,
buf
);
}
static
void
cmd_article
(
int
part
,
char
*
msgid
,
unsigned
long
uid
)
{
int
msgno
,
by_msgid
=
(
msgid
!=
NULL
);
char
*
fname
;
FILE
*
msgfile
;
msgno
=
index_finduid
(
group_state
,
uid
);
if
(
!
msgno
||
index_getuid
(
group_state
,
msgno
)
!=
uid
)
{
prot_printf
(
nntp_out
,
"423 No such article in this newsgroup
\r\n
"
);
return
;
}
fname
=
mailbox_message_fname
(
group_state
->
mailbox
,
uid
);
msgfile
=
fopen
(
fname
,
"r"
);
if
(
!
msgfile
)
{
prot_printf
(
nntp_out
,
"502 Could not read message file
\r\n
"
);
return
;
}
if
(
!
by_msgid
)
{
nntp_current
=
msgno
;
msgid
=
index_get_msgid
(
group_state
,
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'
)
{
/* blank line between header and body */
body
=
1
;
if
(
output
)
{
/* add the Xref header */
struct
buf
xref
=
BUF_INITIALIZER
;
build_xref
(
msgid
,
&
xref
,
0
);
prot_printf
(
nntp_out
,
"%s
\r\n
"
,
buf_cstring
(
&
xref
));
buf_free
(
&
xref
);
}
if
(
part
==
ARTICLE_HEAD
)
{
/* we're done */
break
;
}
else
if
(
part
==
ARTICLE_BODY
)
{
/* start outputing text */
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
));
}
}
/* Protect against messages not ending in CRLF */
if
(
buf
[
strlen
(
buf
)
-1
]
!=
'\n'
)
prot_printf
(
nntp_out
,
"
\r\n
"
);
prot_printf
(
nntp_out
,
".
\r\n
"
);
/* Reset inactivity timer in case we spend a long time
pushing data to the client over a slow link. */
prot_resettimeout
(
nntp_in
);
}
if
(
!
by_msgid
)
free
(
msgid
);
fclose
(
msgfile
);
}
static
void
cmd_authinfo_user
(
char
*
user
)
{
const
char
*
p
;
if
(
nntp_authstate
)
{
prot_printf
(
nntp_out
,
"502 Already authenticated
\r\n
"
);
return
;
}
/* possibly disallow USER */
if
(
!
(
nntp_starttls_done
||
(
extprops_ssf
>
1
)
||
config_getswitch
(
IMAPOPT_ALLOWPLAINTEXT
)))
{
prot_printf
(
nntp_out
,
"483 AUTHINFO USER command only available under a layer
\r\n
"
);
return
;
}
if
(
nntp_userid
)
{
free
(
nntp_userid
);
nntp_userid
=
NULL
;
}
if
(
!
(
p
=
canonify_userid
(
user
,
NULL
,
NULL
)))
{
prot_printf
(
nntp_out
,
"481 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
)
{
int
failedloginpause
;
/* Conceal password in telemetry log */
if
(
nntp_logfd
!=
-1
&&
pass
)
{
(
void
)
ftruncate
(
nntp_logfd
,
lseek
(
nntp_logfd
,
-2
,
SEEK_CUR
)
-
strlen
(
pass
));
(
void
)
write
(
nntp_logfd
,
"...
\r\n
"
,
5
);
}
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
,
"481 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
));
failedloginpause
=
config_getint
(
IMAPOPT_FAILEDLOGINPAUSE
);
if
(
failedloginpause
!=
0
)
{
sleep
(
failedloginpause
);
}
prot_printf
(
nntp_out
,
"481 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 may have been set as a side effect
* of sasl_checkpass() calling mysasl_proxy_policy */
if
(
nntp_authstate
)
auth_freestate
(
nntp_authstate
);
nntp_authstate
=
auth_newstate
(
nntp_userid
);
/* Close IP-based telemetry log and create new log based on userid */
if
(
nntp_logfd
!=
-1
)
close
(
nntp_logfd
);
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
;
sasl_ssf_t
ssf
;
char
*
ssfmsg
=
NULL
;
const
void
*
val
;
int
failedloginpause
;
struct
proc_limits
limits
;
/* Conceal initial response in telemetry log */
if
(
nntp_logfd
!=
-1
&&
resp
)
{
r
=
ftruncate
(
nntp_logfd
,
lseek
(
nntp_logfd
,
-2
,
SEEK_CUR
)
-
strlen
(
resp
));
r
=
write
(
nntp_logfd
,
"...
\r\n
"
,
5
);
r
=
0
;
}
if
(
nntp_userid
)
{
prot_printf
(
nntp_out
,
"502 Already authenticated
\r\n
"
);
return
;
}
/* Stop telemetry logging during SASL exchange */
if
(
nntp_logfd
!=
-1
&&
mech
)
{
prot_setlog
(
nntp_in
,
PROT_NO_FD
);
prot_setlog
(
nntp_out
,
PROT_NO_FD
);
}
if
(
cmd
[
0
]
==
'g'
)
{
/* AUTHINFO GENERIC */
if
(
!
mech
)
{
/* If client didn't specify any mech we give them the list */
const
char
*
sasllist
;
int
mechnum
;
prot_printf
(
nntp_out
,
"281 List of mechanisms follows
\r\n
"
);
/* CRLF separated, dot terminated */
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
{
/* AUTHINFO SASL */
r
=
saslserver
(
nntp_saslconn
,
mech
,
resp
,
""
,
"383 "
,
"="
,
nntp_in
,
nntp_out
,
&
sasl_result
,
&
success_data
);
}
/* Restart any telemetry logging */
prot_setlog
(
nntp_in
,
nntp_logfd
);
prot_setlog
(
nntp_out
,
nntp_logfd
);
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
:
/* failed authentication */
switch
(
sasl_result
)
{
case
SASL_NOMECH
:
case
SASL_TOOWEAK
:
code
=
503
;
break
;
case
SASL_ENCRYPT
:
code
=
483
;
break
;
case
SASL_BADPROT
:
code
=
482
;
break
;
default
:
code
=
481
;
}
syslog
(
LOG_NOTICE
,
"badlogin: %s %s [%s]"
,
nntp_clienthost
,
mech
,
sasl_errdetail
(
nntp_saslconn
));
failedloginpause
=
config_getint
(
IMAPOPT_FAILEDLOGINPAUSE
);
if
(
failedloginpause
!=
0
)
{
sleep
(
failedloginpause
);
}
/* Don't allow user probing */
if
(
sasl_result
==
SASL_NOUSER
)
sasl_result
=
SASL_BADAUTH
;
errorstring
=
sasl_errstring
(
sasl_result
,
NULL
,
NULL
);
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
;
}
/* successful authentication */
/* get the userid from SASL --- already canonicalized from
* mysasl_proxy_policy()
*/
sasl_result
=
sasl_getprop
(
nntp_saslconn
,
SASL_USERNAME
,
&
val
);
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
;
}
nntp_userid
=
xstrdup
((
const
char
*
)
val
);
sasl_getprop
(
nntp_saslconn
,
SASL_SSF
,
&
val
);
ssf
=
*
((
sasl_ssf_t
*
)
val
);
/* really, we should be doing a sasl_getprop on SASL_SSF_EXTERNAL,
but the current libsasl doesn't allow that. */
if
(
nntp_starttls_done
)
{
switch
(
ssf
)
{
case
0
:
ssfmsg
=
"tls protection"
;
break
;
case
1
:
ssfmsg
=
"tls plus integrity protection"
;
break
;
default
:
ssfmsg
=
"tls plus privacy protection"
;
break
;
}
}
else
{
switch
(
ssf
)
{
case
0
:
ssfmsg
=
"no protection"
;
break
;
case
1
:
ssfmsg
=
"integrity protection"
;
break
;
default
:
ssfmsg
=
"privacy protection"
;
break
;
}
}
limits
.
procname
=
"nntpd"
;
limits
.
clienthost
=
nntp_clienthost
;
limits
.
userid
=
nntp_userid
;
if
(
proc_checklimits
(
&
limits
))
{
const
char
*
sep
=
""
;
prot_printf
(
nntp_out
,
"452 Too many open connections ("
);
if
(
limits
.
maxhost
)
{
prot_printf
(
nntp_out
,
"%s%d of %d from %s"
,
sep
,
limits
.
host
,
limits
.
maxhost
,
nntp_clienthost
);
sep
=
", "
;
}
if
(
limits
.
maxuser
)
{
prot_printf
(
nntp_out
,
"%s%d of %d for %s"
,
sep
,
limits
.
user
,
limits
.
maxuser
,
nntp_userid
);
}
prot_printf
(
nntp_out
,
")
\r\n
"
);
reset_saslconn
(
&
nntp_saslconn
);
free
(
nntp_userid
);
nntp_userid
=
NULL
;
return
;
}
syslog
(
LOG_NOTICE
,
"login: %s %s %s%s %s"
,
nntp_clienthost
,
nntp_userid
,
mech
,
nntp_starttls_done
?
"+TLS"
:
""
,
"User logged in"
);
proc_register
(
"nntpd"
,
nntp_clienthost
,
nntp_userid
,
NULL
);
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
);
/* Close IP-based telemetry log and create new log based on userid */
if
(
nntp_logfd
!=
-1
)
close
(
nntp_logfd
);
nntp_logfd
=
telemetry_log
(
nntp_userid
,
nntp_in
,
nntp_out
,
0
);
if
(
ssf
)
{
/* close any selected group */
if
(
group_state
)
index_close
(
&
group_state
);
if
(
backend_current
)
{
proxy_downserver
(
backend_current
);
backend_current
=
NULL
;
}
}
}
static
void
cmd_hdr
(
char
*
cmd
,
char
*
hdr
,
char
*
pat
,
char
*
msgid
,
unsigned
long
uid
,
unsigned
long
last
)
{
int
msgno
,
last_msgno
;
int
by_msgid
=
(
msgid
!=
NULL
);
int
found
=
0
;
lcase
(
hdr
);
msgno
=
index_finduid
(
group_state
,
uid
);
if
(
!
msgno
||
index_getuid
(
group_state
,
msgno
)
!=
uid
)
msgno
++
;
last_msgno
=
index_finduid
(
group_state
,
last
);
for
(;
msgno
<=
last_msgno
;
msgno
++
)
{
char
*
body
;
if
(
!
found
++
)
prot_printf
(
nntp_out
,
"%u Headers follow:
\r\n
"
,
cmd
[
0
]
==
'X'
?
221
:
225
);
/* see if we're looking for metadata */
if
(
hdr
[
0
]
==
':'
)
{
if
(
!
strcasecmp
(
":bytes"
,
hdr
))
{
struct
buf
xref
=
BUF_INITIALIZER
;
unsigned
long
size
=
index_getsize
(
group_state
,
msgno
);
if
(
!
by_msgid
)
msgid
=
index_get_msgid
(
group_state
,
msgno
);
build_xref
(
msgid
,
&
xref
,
0
);
if
(
!
by_msgid
)
free
(
msgid
);
prot_printf
(
nntp_out
,
"%lu %lu
\r\n
"
,
by_msgid
?
0
:
uid
,
size
+
xref
.
len
+
2
);
/* +2 for \r\n */
buf_free
(
&
xref
);
}
else
if
(
!
strcasecmp
(
":lines"
,
hdr
))
prot_printf
(
nntp_out
,
"%u %lu
\r\n
"
,
by_msgid
?
0
:
index_getuid
(
group_state
,
msgno
),
index_getlines
(
group_state
,
msgno
));
else
prot_printf
(
nntp_out
,
"%u
\r\n
"
,
by_msgid
?
0
:
index_getuid
(
group_state
,
msgno
));
}
else
if
(
!
strcmp
(
hdr
,
"xref"
)
&&
!
pat
/* [X]HDR only */
)
{
struct
buf
xref
=
BUF_INITIALIZER
;
if
(
!
by_msgid
)
msgid
=
index_get_msgid
(
group_state
,
msgno
);
build_xref
(
msgid
,
&
xref
,
1
);
if
(
!
by_msgid
)
free
(
msgid
);
prot_printf
(
nntp_out
,
"%u %s
\r\n
"
,
by_msgid
?
0
:
index_getuid
(
group_state
,
msgno
),
buf_cstring
(
&
xref
));
buf_free
(
&
xref
);
}
else
if
((
body
=
index_getheader
(
group_state
,
msgno
,
hdr
))
&&
(
!
pat
||
/* [X]HDR */
wildmat
(
body
,
pat
)))
{
/* XPAT with match */
prot_printf
(
nntp_out
,
"%u %s
\r\n
"
,
by_msgid
?
0
:
index_getuid
(
group_state
,
msgno
),
body
);
}
}
if
(
found
)
prot_printf
(
nntp_out
,
".
\r\n
"
);
else
prot_printf
(
nntp_out
,
"423 No such article(s)
in
this
newsgroup
\
r
\
n
")
;
}
static
void
cmd_help
(
void
)
{
prot_printf
(
nntp_out
,
"100 Supported commands:
\r\n
"
);
if
((
nntp_capa
&
MODE_READ
)
&&
(
nntp_authstate
||
allowanonymous
))
{
prot_printf
(
nntp_out
,
"
\t
ARTICLE [ message-id | number ]
\r\n
"
"
\t\t
Retrieve entirety of the specified article.
\r\n
"
);
}
if
(
!
nntp_authstate
)
{
if
(
!
nntp_userid
)
{
prot_printf
(
nntp_out
,
"
\t
AUTHINFO SASL mechanism [initial-response]
\r\n
"
"
\t\t
Perform an authentication exchange using the specified
\r\n
"
"
\t\t
SASL mechanism.
\r\n
"
);
prot_printf
(
nntp_out
,
"
\t
AUTHINFO USER username
\r\n
"
"
\t\t
Present username for authentication.
\r\n
"
);
}
prot_printf
(
nntp_out
,
"
\t
AUTHINFO PASS password
\r\n
"
"
\t\t
Present clear-text password for authentication.
\r\n
"
);
}
if
((
nntp_capa
&
MODE_READ
)
&&
(
nntp_authstate
||
allowanonymous
))
{
prot_printf
(
nntp_out
,
"
\t
BODY [ message-id | number ]
\r\n
"
"
\t\t
Retrieve body of the specified article.
\r\n
"
);
}
prot_printf
(
nntp_out
,
"
\t
CAPABILITIES
\r\n
"
"
\t\t
List the current server capabilities.
\r\n
"
);
if
(
nntp_capa
&
MODE_FEED
)
{
prot_printf
(
nntp_out
,
"
\t
CHECK message-id
\r\n
"
"
\t\t
Check if the server wants the specified article.
\r\n
"
);
}
if
((
nntp_capa
&
MODE_READ
)
&&
(
nntp_authstate
||
allowanonymous
))
{
prot_printf
(
nntp_out
,
"
\t
DATE
\r\n
"
"
\t\t
Request the current server UTC date and time.
\r\n
"
);
prot_printf
(
nntp_out
,
"
\t
GROUP group
\r\n
"
"
\t\t
Select a newsgroup for article retrieval.
\r\n
"
);
prot_printf
(
nntp_out
,
"
\t
HDR header [ message-id | range ]
\r\n
"
"
\t\t
Retrieve the specified header/metadata from the
\r\n
"
"
\t\t
specified article(s).
\r\n
"
);
}
prot_printf
(
nntp_out
,
"
\t
HEAD [ message-id | number ]
\r\n
"
"
\t\t
Retrieve the headers of the specified article.
\r\n
"
);
prot_printf
(
nntp_out
,
"
\t
HELP
\r\n
"
"
\t\t
Request command summary (this text).
\r\n
"
);
if
(
nntp_capa
&
MODE_FEED
)
{
prot_printf
(
nntp_out
,
"
\t
IHAVE message-id
\r\n
"
"
\t\t
Present/transfer the specified article to the server.
\r\n
"
);
}
if
((
nntp_capa
&
MODE_READ
)
&&
(
nntp_authstate
||
allowanonymous
))
{
prot_printf
(
nntp_out
,
"
\t
LAST
\r\n
"
"
\t\t
Select the previous article.
\r\n
"
);
}
prot_printf
(
nntp_out
,
"
\t
LIST [ ACTIVE wildmat ]
\r\n
"
"
\t\t
List the (subset of) valid newsgroups.
\r\n
"
);
if
((
nntp_capa
&
MODE_READ
)
&&
(
nntp_authstate
||
allowanonymous
))
{
prot_printf
(
nntp_out
,
"
\t
LIST HEADERS [ MSGID | RANGE ]
\r\n
"
"
\t\t
List the headers and metadata items available via HDR.
\r\n
"
);
prot_printf
(
nntp_out
,
"
\t
LIST NEWSGROUPS [wildmat]
\r\n
"
"
\t\t
List the descriptions of the specified newsgroups.
\r\n
"
);
prot_printf
(
nntp_out
,
"
\t
LIST OVERVIEW.FMT
\r\n
"
"
\t\t
List the headers and metadata items available via OVER.
\r\n
"
);
prot_printf
(
nntp_out
,
"
\t
LISTGROUP [group [range]]
\r\n
"
"
\t\t
List the article numbers in the specified newsgroup.
\r\n
"
);
if
(
config_getswitch
(
IMAPOPT_ALLOWNEWNEWS
))
prot_printf
(
nntp_out
,
"
\t
NEWNEWS wildmat date time [GMT]
\r\n
"
"
\t\t
List the newly arrived articles in the specified newsgroup(s)
\r\n
"
"
\t\t
since the specified date and time.
\r\n
"
);
prot_printf
(
nntp_out
,
"
\t
NEXT
\r\n
"
"
\t\t
Select the next article.
\r\n
"
);
prot_printf
(
nntp_out
,
"
\t
OVER [ message-id | range ]
\r\n
"
"
\t\t
Retrieve the overview information for the specified article(s).
\r\n
"
);
prot_printf
(
nntp_out
,
"
\t
POST
\r\n
"
"
\t\t
Post an article to the server.
\r\n
"
);
}
prot_printf
(
nntp_out
,
"
\t
QUIT
\r\n
"
"
\t\t
Terminate the session.
\r\n
"
);
if
(
tls_enabled
()
&&
!
nntp_starttls_done
&&
!
nntp_authstate
)
{
prot_printf
(
nntp_out
,
"
\t
STARTTLS
\r\n
"
"
\t\t
Start a TLS negotiation.
\r\n
"
);
}
prot_printf
(
nntp_out
,
"
\t
STAT [ message-id | number ]
\r\n
"
"
\t\t
Check if the specified article exists.
\r\n
"
);
if
(
nntp_capa
&
MODE_FEED
)
{
prot_printf
(
nntp_out
,
"
\t
TAKETHIS message-id
\r\n
"
"
\t\t
Transfer the specified article to the server.
\r\n
"
);
}
if
((
nntp_capa
&
MODE_READ
)
&&
(
nntp_authstate
||
allowanonymous
))
{
prot_printf
(
nntp_out
,
"
\t
XPAT header message-id|range wildmat
\r\n
"
"
\t\t
List the specified article(s) in which the contents
\r\n
"
"
\t\t
of the specified header/metadata matches the wildmat.
\r\n
"
);
}
prot_printf
(
nntp_out
,
".
\r\n
"
);
}
struct
list_rock
{
int
(
*
proc
)(
char
*
,
void
*
);
struct
wildmat
*
wild
;
struct
hash_table
server_table
;
};
/*
* mboxlist_findall() callback function to LIST
*/
static
int
list_cb
(
char
*
name
,
int
matchlen
,
int
maycreate
__attribute__
((
unused
)),
void
*
rock
)
{
static
char
lastname
[
MAX_MAILBOX_BUFFER
];
struct
list_rock
*
lrock
=
(
struct
list_rock
*
)
rock
;
struct
wildmat
*
wild
;
/* We have to reset the initial state.
* Handle it as a dirty hack.
*/
if
(
!
name
)
{
lastname
[
0
]
=
'\0'
;
return
0
;
}
/* skip mailboxes that we don't want to serve as newsgroups */
if
(
!
is_newsgroup
(
name
))
return
0
;
/* don't repeat */
if
(
matchlen
==
(
int
)
strlen
(
lastname
)
&&
!
strncmp
(
name
,
lastname
,
matchlen
))
return
0
;
strncpy
(
lastname
,
name
,
matchlen
);
lastname
[
matchlen
]
=
'\0'
;
/* see if the mailbox matches one of our specified wildmats */
wild
=
lrock
->
wild
;
while
(
wild
->
pat
&&
wildmat
(
name
,
wild
->
pat
)
!=
1
)
wild
++
;
/* if we don't have a match, or its a negative match, skip it */
if
(
!
wild
->
pat
||
wild
->
not
)
return
0
;
return
lrock
->
proc
(
name
,
lrock
);
}
struct
enum_rock
{
const
char
*
cmd
;
char
*
wild
;
};
/*
* hash_enumerate() callback function to LIST (proxy)
*/
static
void
list_proxy
(
const
char
*
server
,
void
*
data
__attribute__
((
unused
)),
void
*
rock
)
{
struct
enum_rock
*
erock
=
(
struct
enum_rock
*
)
rock
;
struct
backend
*
be
;
int
r
;
char
*
result
;
be
=
proxy_findserver
(
server
,
&
nntp_protocol
,
nntp_authstate
?
nntp_userid
:
"anonymous"
,
&
backend_cached
,
&
backend_current
,
NULL
,
nntp_in
);
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
);
}
}
}
/*
* perform LIST ACTIVE (backend) or create a server hash table (proxy)
*/
static
int
do_active
(
char
*
name
,
void
*
rock
)
{
struct
list_rock
*
lrock
=
(
struct
list_rock
*
)
rock
;
int
r
,
postable
;
struct
backend
*
be
;
/* open the group */
r
=
open_group
(
name
,
1
,
&
be
,
&
postable
);
if
(
r
)
{
/* can't open group, skip it */
}
else
if
(
be
)
{
if
(
!
hash_lookup
(
be
->
hostname
,
&
lrock
->
server_table
))
{
/* add this server to our table */
hash_insert
(
be
->
hostname
,
(
void
*
)
0xDEADBEEF
,
&
lrock
->
server_table
);
}
}
else
{
prot_printf
(
nntp_out
,
"%s %u %u %c
\r\n
"
,
name
+
strlen
(
newsprefix
),
group_state
->
exists
?
index_getuid
(
group_state
,
group_state
->
exists
)
:
group_state
->
mailbox
->
i
.
last_uid
,
group_state
->
exists
?
index_getuid
(
group_state
,
1
)
:
group_state
->
mailbox
->
i
.
last_uid
+
1
,
postable
?
'y'
:
'n'
);
index_close
(
&
group_state
);
}
return
0
;
}
/*
* perform LIST NEWSGROUPS (backend) or create a server hash table (proxy)
*/
static
int
do_newsgroups
(
char
*
name
,
void
*
rock
)
{
struct
list_rock
*
lrock
=
(
struct
list_rock
*
)
rock
;
struct
mboxlist_entry
*
mbentry
=
NULL
;
int
r
;
r
=
mlookup
(
name
,
&
mbentry
);
if
(
r
||
!
mbentry
->
acl
||
!
(
cyrus_acl_myrights
(
nntp_authstate
,
mbentry
->
acl
)
&&
ACL_LOOKUP
))
{
mboxlist_entry_free
(
&
mbentry
);
return
0
;
}
if
(
mbentry
->
server
)
{
/* remote group */
if
(
!
hash_lookup
(
mbentry
->
server
,
&
lrock
->
server_table
))
{
/* add this server to our table */
hash_insert
(
mbentry
->
server
,
(
void
*
)
0xDEADBEEF
,
&
lrock
->
server_table
);
}
mboxlist_entry_free
(
&
mbentry
);
}
else
{
/* local group */
mboxlist_entry_free
(
&
mbentry
);
return
CYRUSDB_DONE
;
}
return
0
;
}
/*
* annotatemore_findall() callback function to LIST NEWSGROUPS
*/
static
int
newsgroups_cb
(
const
char
*
mailbox
,
uint32_t
uid
__attribute__
((
unused
)),
const
char
*
entry
__attribute__
((
unused
)),
const
char
*
userid
,
const
struct
buf
*
value
,
void
*
rock
)
{
struct
wildmat
*
wild
=
(
struct
wildmat
*
)
rock
;
/* skip personal mailboxes */
if
((
!
strncasecmp
(
mailbox
,
"INBOX"
,
5
)
&&
(
!
mailbox
[
5
]
||
mailbox
[
5
]
==
'.'
))
||
!
strncmp
(
mailbox
,
"user."
,
5
))
return
0
;
/* see if the mailbox matches one of our wildmats */
while
(
wild
->
pat
&&
wildmat
(
mailbox
,
wild
->
pat
)
!=
1
)
wild
++
;
/* if we don't have a match, or its a negative match, skip it */
if
(
!
wild
->
pat
||
wild
->
not
)
return
0
;
/* we only care about shared /comment */
if
(
userid
[
0
])
return
0
;
prot_printf
(
nntp_out
,
"%s
\t
%s
\r\n
"
,
mailbox
+
strlen
(
newsprefix
),
value
->
s
);
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_BUFFER
];
struct
list_rock
lrock
;
struct
enum_rock
erock
;
if
(
!
arg2
)
arg2
=
"*"
;
erock
.
cmd
=
"ACTIVE"
;
erock
.
wild
=
xstrdup
(
arg2
);
/* make a copy before we munge it */
lrock
.
proc
=
do_active
;
lrock
.
wild
=
split_wildmats
(
arg2
);
/* split the list of wildmats */
/* xxx better way to determine a size for this table? */
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_authstate
?
nntp_userid
:
NULL
,
nntp_authstate
,
list_cb
,
&
lrock
);
/* proxy to the backends */
hash_enumerate
(
&
lrock
.
server_table
,
list_proxy
,
&
erock
);
prot_printf
(
nntp_out
,
".
\r\n
"
);
/* free the hash table */
free_hash_table
(
&
lrock
.
server_table
,
NULL
);
/* free the wildmats */
free_wildmats
(
lrock
.
wild
);
free
(
erock
.
wild
);
if
(
group_state
)
index_close
(
&
group_state
);
}
else
if
(
!
(
nntp_capa
&
MODE_READ
))
{
prot_printf
(
nntp_out
,
"502 Permission denied
\r\n
"
);
return
;
}
else
if
(
!
nntp_authstate
&&
!
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
"
);
/* all headers */
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_BUFFER
];
struct
list_rock
lrock
;
struct
enum_rock
erock
;
if
(
!
arg2
)
arg2
=
"*"
;
erock
.
cmd
=
"NEWSGROUPS"
;
erock
.
wild
=
xstrdup
(
arg2
);
/* make a copy before we munge it */
lrock
.
proc
=
do_newsgroups
;
lrock
.
wild
=
split_wildmats
(
arg2
);
/* split the list of wildmats */
/* xxx better way to determine a size for this table? */
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_authstate
?
nntp_userid
:
NULL
,
nntp_authstate
,
list_cb
,
&
lrock
);
/* proxy to the backends */
hash_enumerate
(
&
lrock
.
server_table
,
list_proxy
,
&
erock
);
strcpy
(
pattern
,
newsprefix
);
strcat
(
pattern
,
"*"
);
annotatemore_findall
(
pattern
,
0
,
"/comment"
,
newsgroups_cb
,
lrock
.
wild
);
prot_printf
(
nntp_out
,
".
\r\n
"
);
/* free the hash table */
free_hash_table
(
&
lrock
.
server_table
,
NULL
);
/* free the wildmats */
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
)
{
/* new OVER format */
prot_printf
(
nntp_out
,
":bytes
\r\n
"
);
prot_printf
(
nntp_out
,
":lines
\r\n
"
);
}
else
{
/* old XOVER format */
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"
,
(
nntp_capa
&
MODE_READ
)
?
200
:
201
);
if
(
config_serverinfo
||
nntp_authstate
)
{
prot_printf
(
nntp_out
,
" %s"
,
config_servername
);
}
if
(
nntp_authstate
||
(
config_serverinfo
==
IMAP_ENUM_SERVERINFO_ON
))
{
prot_printf
(
nntp_out
,
" Cyrus NNTP%s %s"
,
config_mupdate_server
?
" Murder"
:
""
,
cyrus_version
());
}
prot_printf
(
nntp_out
,
" server ready, posting %s
\r\n
"
,
(
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");
/* Do search of annotations here. */
prot_printf(nntp_out, ".\r\n");
#endif
}
/*
* duplicate_find() callback function to list NEWNEWS
*/
struct
newrock
{
time_t
tstamp
;
struct
wildmat
*
wild
;
char
lastid
[
1024
];
};
static
int
newnews_cb
(
const
duplicate_key_t
*
dkey
,
time_t
mark
,
unsigned
long
uid
,
void
*
rock
)
{
struct
newrock
*
nrock
=
(
struct
newrock
*
)
rock
;
/* Make sure we don't return duplicate msgids,
* the message is newer than the tstamp, and
* the message is in mailbox we serve as a newsgroup..
*/
if
(
strcmp
(
dkey
->
id
,
nrock
->
lastid
)
&&
mark
>=
nrock
->
tstamp
&&
uid
&&
is_newsgroup
(
dkey
->
to
))
{
struct
wildmat
*
wild
=
nrock
->
wild
;
/* see if the mailbox matches one of our specified wildmats */
while
(
wild
->
pat
&&
wildmat
(
dkey
->
to
,
wild
->
pat
)
!=
1
)
wild
++
;
/* we have a match, and its not a negative match */
if
(
wild
->
pat
&&
!
wild
->
not
)
{
prot_printf
(
nntp_out
,
"%s
\r\n
"
,
dkey
->
id
);
strlcpy
(
nrock
->
lastid
,
dkey
->
id
,
sizeof
(
nrock
->
lastid
));
}
}
return
0
;
}
static
void
cmd_newnews
(
char
*
wild
,
time_t
tstamp
)
{
struct
newrock
nrock
;
memset
(
&
nrock
,
0
,
sizeof
(
nrock
));
nrock
.
tstamp
=
tstamp
;
nrock
.
wild
=
split_wildmats
(
wild
);
prot_printf
(
nntp_out
,
"230 List of new articles follows:
\r\n
"
);
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
)
{
uint32_t
msgno
,
last_msgno
;
struct
nntp_overview
*
over
;
int
found
=
0
;
msgno
=
index_finduid
(
group_state
,
uid
);
if
(
!
msgno
||
index_getuid
(
group_state
,
msgno
)
!=
uid
)
msgno
++
;
last_msgno
=
index_finduid
(
group_state
,
last
);
for
(;
msgno
<=
last_msgno
;
msgno
++
)
{
if
(
!
found
++
)
prot_printf
(
nntp_out
,
"224 Overview information follows:
\r\n
"
);
if
((
over
=
index_overview
(
group_state
,
msgno
)))
{
struct
buf
xref
=
BUF_INITIALIZER
;
build_xref
(
over
->
msgid
,
&
xref
,
0
);
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
+
xref
.
len
+
2
,
/* +2 for \r\n */
over
->
lines
,
buf_cstring
(
&
xref
));
buf_free
(
&
xref
);
}
}
if
(
found
)
prot_printf
(
nntp_out
,
".
\r\n
"
);
else
prot_printf
(
nntp_out
,
"423 No such article(s)
in
this
newsgroup
\
r
\
n
")
;
}
#define RCPT_GROW 30
typedef
struct
message_data
message_data_t
;
struct
message_data
{
struct
protstream
*
data
;
/* message in temp file */
FILE
*
f
;
/* FILE * corresponding */
char
*
id
;
/* message id */
char
*
path
;
/* path */
char
*
control
;
/* control message */
unsigned
long
size
;
/* size of message in bytes */
strarray_t
rcpt
;
/* mailboxes to post message */
char
*
date
;
/* date field of header */
hdrcache_t
hdrcache
;
};
/* returns non-zero on failure */
static
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
;
strarray_init
(
&
ret
->
rcpt
);
ret
->
date
=
NULL
;
ret
->
hdrcache
=
spool_new_hdrcache
();
*
m
=
ret
;
return
0
;
}
static
void
msg_free
(
message_data_t
*
m
)
{
if
(
m
->
data
)
prot_free
(
m
->
data
);
if
(
m
->
f
)
fclose
(
m
->
f
);
free
(
m
->
id
);
free
(
m
->
path
);
free
(
m
->
control
);
free
(
m
->
date
);
strarray_fini
(
&
m
->
rcpt
);
spool_free_hdrcache
(
m
->
hdrcache
);
free
(
m
);
}
static
void
parse_groups
(
const
char
*
groups
,
message_data_t
*
msg
)
{
const
char
*
p
;
char
*
rcpt
=
NULL
;
char
*
buf
=
xstrdup
(
groups
);
const
char
sep
[]
=
",
\t
"
;
for
(
p
=
strtok
(
buf
,
sep
)
;
p
;
p
=
strtok
(
NULL
,
sep
))
{
/* construct the mailbox name */
free
(
rcpt
);
rcpt
=
strconcat
(
newsprefix
,
p
,
(
char
*
)
NULL
);
/* skip mailboxes that we don't serve as newsgroups */
if
(
!
is_newsgroup
(
rcpt
))
continue
;
/* Only add mailboxes that exist */
if
(
!
mlookup
(
rcpt
,
NULL
))
{
strarray_appendm
(
&
msg
->
rcpt
,
rcpt
);
rcpt
=
NULL
;
}
}
free
(
rcpt
);
free
(
buf
);
}
/* Create a new header to be cached and/or spooled to disk.
* 'destname' contains the name of the new header.
* 'dest' contains an optional existing header body to be appended to.
* 'src' contains an optional existing header body to add to 'dest'.
* 'newspostuser' contains an optional userid used to create "post"
* email addresses from newsgroup names
*/
static
void
add_header
(
const
char
*
destname
,
const
char
**
dest
,
const
char
**
src
,
const
char
*
newspostuser
,
hdrcache_t
hdrcache
,
FILE
*
f
)
{
struct
buf
buf
=
BUF_INITIALIZER
;
char
*
newdest
=
NULL
;
int
fold
=
0
;
if
(
src
)
{
if
(
!
newspostuser
)
{
/* no translation of source needed - copy as-is to dest */
buf_appendcstr
(
&
buf
,
src
[
0
]);
}
else
{
/* translate source newsgroups into "post" email addresses */
const
char
*
s
,
*
sep
=
""
;
size_t
n
;
if
(
dest
)
{
/* append to the existing dest header body */
buf_appendcstr
(
&
buf
,
dest
[
0
]);
fold
=
buf
.
len
+
1
;
sep
=
", "
;
}
for
(
s
=
src
[
0
];;
s
+=
n
)
{
/* skip whitespace */
while
(
s
&&
*
s
&&
(
Uisspace
(
*
s
)
||
*
s
==
','
))
s
++
;
if
(
!
s
||
!*
s
)
break
;
/* find end of source address/group */
n
=
strcspn
(
s
,
",
\t
"
);
/* append the new (translated) address */
buf_printf
(
&
buf
,
"%s%s+%.*s"
,
sep
,
newspostuser
,
(
int
)
n
,
s
);
if
(
config_defdomain
)
buf_printf
(
&
buf
,
"@%s"
,
config_defdomain
);
sep
=
", "
;
}
}
newdest
=
buf_release
(
&
buf
);
if
(
dest
)
{
/* replace the existing cached header */
spool_replace_header
(
xstrdup
(
destname
),
newdest
,
hdrcache
);
}
else
{
/* add the new header to the cache */
spool_cache_header
(
xstrdup
(
destname
),
newdest
,
hdrcache
);
}
}
else
if
(
dest
)
{
/* no source header, use original dest header */
newdest
=
(
char
*
)
dest
[
0
];
}
if
(
newdest
)
{
/* add the new dest header to the spool file */
fprintf
(
f
,
"%s: "
,
destname
);
if
(
fold
)
fprintf
(
f
,
"%.*s
\r\n\t
"
,
fold
,
newdest
);
fprintf
(
f
,
"%s
\r\n
"
,
newdest
+
fold
);
}
}
/*
* file in the message structure 'm' from 'pin', assuming a dot-stuffed
* stream a la nntp.
*
* returns 0 on success, imap error code on failure
*/
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"
,
/* need to prepend our servername */
"Xref"
,
/* need to remove (generated on the fly) */
"To"
,
/* need to add "post" email addresses */
"Reply-To"
,
/* need to add "post" email addresses */
NULL
};
m
->
f
=
f
;
/* fill the cache */
r
=
spool_fill_hdrcache
(
nntp_in
,
f
,
m
->
hdrcache
,
skipheaders
);
if
(
r
)
{
/* got a bad header */
/* flush the remaining output */
spool_copy_msg
(
nntp_in
,
NULL
);
return
r
;
}
/* now, using our header cache, fill in the data that we want */
/* get path */
if
((
body
=
spool_getheader
(
m
->
hdrcache
,
"path"
))
!=
NULL
)
{
/* prepend to the cached path */
m
->
path
=
strconcat
(
config_servername
,
"!"
,
body
[
0
],
(
char
*
)
NULL
);
spool_replace_header
(
xstrdup
(
"Path"
),
xstrdup
(
m
->
path
),
m
->
hdrcache
);
}
else
{
/* no path, create one */
m
->
path
=
strconcat
(
config_servername
,
"!"
,
nntp_userid
?
nntp_userid
:
"anonymous"
,
(
char
*
)
NULL
);
spool_cache_header
(
xstrdup
(
"Path"
),
xstrdup
(
m
->
path
),
m
->
hdrcache
);
}
fprintf
(
f
,
"Path: %s
\r\n
"
,
m
->
path
);
/* get message-id */
if
((
body
=
spool_getheader
(
m
->
hdrcache
,
"message-id"
))
!=
NULL
)
{
m
->
id
=
xstrdup
(
body
[
0
]);
}
else
{
/* no message-id, create one */
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
);
}
/* get date */
if
((
body
=
spool_getheader
(
m
->
hdrcache
,
"date"
))
==
NULL
)
{
/* no date, create one */
char
datestr
[
RFC822_DATETIME_MAX
+
1
];
time_to_rfc822
(
now
,
datestr
,
sizeof
(
datestr
));
m
->
date
=
xstrdup
(
datestr
);
fprintf
(
f
,
"Date: %s
\r\n
"
,
datestr
);
spool_cache_header
(
xstrdup
(
"Date"
),
xstrdup
(
datestr
),
m
->
hdrcache
);
}
else
{
m
->
date
=
xstrdup
(
body
[
0
]);
}
/* get control */
if
((
body
=
spool_getheader
(
m
->
hdrcache
,
"control"
))
!=
NULL
)
{
size_t
len
;
char
*
s
;
m
->
control
=
xstrdup
(
body
[
0
]);
/* create a recipient for the appropriate pseudo newsgroup */
len
=
strcspn
(
m
->
control
,
"
\t\r\n
"
);
s
=
xmalloc
(
strlen
(
newsprefix
)
+
8
+
len
+
1
);
sprintf
(
s
,
"%scontrol.%.*s"
,
newsprefix
,
(
int
)
len
,
m
->
control
);
strarray_appendm
(
&
m
->
rcpt
,
s
);
}
else
{
m
->
control
=
NULL
;
/* no control */
/* get newsgroups */
if
((
groups
=
spool_getheader
(
m
->
hdrcache
,
"newsgroups"
))
!=
NULL
)
{
/* parse newsgroups and create recipients */
parse_groups
(
groups
[
0
],
m
);
if
(
!
m
->
rcpt
.
count
)
{
r
=
IMAP_MAILBOX_NONEXISTENT
;
/* no newsgroups that we serve */
}
if
(
!
r
)
{
const
char
*
newspostuser
=
config_getstring
(
IMAPOPT_NEWSPOSTUSER
);
unsigned
long
newsaddheaders
=
config_getbitfield
(
IMAPOPT_NEWSADDHEADERS
);
const
char
**
to
=
NULL
,
**
replyto
=
NULL
;
/* add To: header to spooled message file,
optionally adding "post" email addr based on newsgroup */
body
=
spool_getheader
(
m
->
hdrcache
,
"to"
);
if
(
newspostuser
&&
(
newsaddheaders
&
IMAP_ENUM_NEWSADDHEADERS_TO
))
{
to
=
groups
;
}
add_header
(
"To"
,
body
,
to
,
newspostuser
,
m
->
hdrcache
,
f
);
/* add Reply-To: header to spooled message file,
optionally adding "post" email addr based on newsgroup */
body
=
spool_getheader
(
m
->
hdrcache
,
"reply-to"
);
if
(
newspostuser
&&
(
newsaddheaders
&
IMAP_ENUM_NEWSADDHEADERS_REPLYTO
))
{
/* determine which groups header to use for reply-to */
replyto
=
spool_getheader
(
m
->
hdrcache
,
"followup-to"
);
if
(
!
replyto
)
replyto
=
groups
;
else
if
(
!
strncasecmp
(
replyto
[
0
],
"poster"
,
strcspn
(
replyto
[
0
],
"
\t
"
)))
{
/* reply doesn't go to group */
newspostuser
=
NULL
;
if
(
body
)
replyto
=
NULL
;
else
replyto
=
spool_getheader
(
m
->
hdrcache
,
"from"
);
}
}
add_header
(
"Reply-To"
,
body
,
replyto
,
newspostuser
,
m
->
hdrcache
,
f
);
}
}
else
{
r
=
NNTP_NO_NEWSGROUPS
;
/* no newsgroups header */
}
if
(
r
)
{
/* error getting newsgroups */
/* flush the remaining output */
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
;
}
/* spool to the stage of one of the recipients */
for
(
i
=
0
;
!
stagef
&&
(
i
<
m
->
rcpt
.
count
);
i
++
)
{
stagef
=
append_newstage
(
m
->
rcpt
.
data
[
i
],
now
,
0
,
&
stage
);
}
if
(
stagef
)
{
const
char
*
base
=
0
;
size_t
size
=
0
;
int
n
;
/* copy the header from our tmpfile to the stage */
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
)
{
/* close and remove the stage */
fclose
(
stagef
);
append_removestage
(
stage
);
stage
=
NULL
;
return
IMAP_IOERROR
;
}
else
{
/* close the tmpfile and use the stage */
fclose
(
f
);
m
->
f
=
f
=
stagef
;
}
}
/* else this is probably a remote group, so use the tmpfile */
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
->
data
=
prot_new
(
fileno
(
f
),
0
);
return
0
;
}
static
int
deliver_remote
(
message_data_t
*
msg
,
struct
dest
*
dlist
)
{
struct
dest
*
d
;
/* run the txns */
for
(
d
=
dlist
;
d
;
d
=
d
->
next
)
{
struct
backend
*
be
;
char
buf
[
4096
];
be
=
proxy_findserver
(
d
->
server
,
&
nntp_protocol
,
nntp_authstate
?
nntp_userid
:
"anonymous"
,
&
backend_cached
,
&
backend_current
,
NULL
,
nntp_in
);
if
(
!
be
)
return
IMAP_SERVER_UNAVAILABLE
;
/* tell the backend about our new article */
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
;
}
/* send the article */
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
));
}
/* Protect against messages not ending in CRLF */
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
;
}
}
return
0
;
}
static
int
deliver
(
message_data_t
*
msg
)
{
int
n
,
r
=
0
,
myrights
;
char
*
rcpt
=
NULL
;
struct
body
*
body
=
NULL
;
struct
dest
*
dlist
=
NULL
;
struct
mboxlist_entry
*
mbentry
=
NULL
;
duplicate_key_t
dkey
=
{
msg
->
id
,
NULL
,
msg
->
date
};
/* check ACLs of all mailboxes */
for
(
n
=
0
;
n
<
msg
->
rcpt
.
count
;
n
++
)
{
rcpt
=
msg
->
rcpt
.
data
[
n
];
dkey
.
to
=
rcpt
;
/* free the mbentry from the last iteration */
mboxlist_entry_free
(
&
mbentry
);
/* look it up */
r
=
mlookup
(
rcpt
,
&
mbentry
);
if
(
r
)
return
IMAP_MAILBOX_NONEXISTENT
;
if
(
!
(
mbentry
->
acl
&&
(
myrights
=
cyrus_acl_myrights
(
nntp_authstate
,
mbentry
->
acl
))
&&
(
myrights
&
ACL_POST
)))
{
mboxlist_entry_free
(
&
mbentry
);
return
IMAP_PERMISSION_DENIED
;
}
if
(
mbentry
->
server
)
{
/* remote group */
proxy_adddest
(
&
dlist
,
NULL
,
0
,
mbentry
->
server
,
""
);
}
else
{
/* local group */
struct
appendstate
as
;
static
const
quota_t
qdiffs
[
QUOTA_NUMRESOURCES
]
=
QUOTA_DIFFS_INITIALIZER
;
if
(
msg
->
id
&&
duplicate_check
(
&
dkey
))
{
/* duplicate message */
duplicate_log
(
&
dkey
,
"nntp delivery"
);
continue
;
}
r
=
append_setup
(
&
as
,
rcpt
,
nntp_authstate
?
nntp_userid
:
NULL
,
nntp_authstate
,
ACL_POST
,
qdiffs
,
NULL
,
0
,
0
);
if
(
!
r
)
{
prot_rewind
(
msg
->
data
);
if
(
stage
)
{
r
=
append_fromstage
(
&
as
,
&
body
,
stage
,
0
,
NULL
,
!
singleinstance
,
/*annotations*/
NULL
);
}
else
{
/* XXX should never get here */
r
=
append_fromstream
(
&
as
,
&
body
,
msg
->
data
,
msg
->
size
,
0
,
NULL
);
}
if
(
r
||
(
msg
->
id
&&
duplicate_check
(
&
dkey
)
)
)
{
append_abort
(
&
as
);
if
(
!
r
)
{
/* duplicate message */
duplicate_log
(
&
dkey
,
"nntp delivery"
);
continue
;
}
}
else
{
r
=
append_commit
(
&
as
);
}
}
if
(
!
r
&&
msg
->
id
)
duplicate_mark
(
&
dkey
,
time
(
NULL
),
as
.
baseuid
);
if
(
r
)
{
mboxlist_entry_free
(
&
mbentry
);
return
r
;
}
}
}
mboxlist_entry_free
(
&
mbentry
);
if
(
body
)
{
message_free_body
(
body
);
free
(
body
);
}
if
(
dlist
)
{
struct
dest
*
d
;
/* run the txns */
r
=
deliver_remote
(
msg
,
dlist
);
/* free the destination list */
d
=
dlist
;
while
(
d
)
{
struct
dest
*
nextd
=
d
->
next
;
free
(
d
);
d
=
nextd
;
}
}
return
r
;
}
#if 0
/* XXX Need to review control message auth/authz and implementation */
static int newgroup(message_data_t *msg)
{
int r;
char *group;
char mailboxname[MAX_MAILBOX_BUFFER];
/* isolate newsgroup */
group = msg->control + 8; /* skip "newgroup" */
while (Uisspace(*group)) group++;
snprintf(mailboxname, sizeof(mailboxname), "%s%.*s",
newsprefix, (int) strcspn(group, " \t\r\n"), group);
/* XXX do we need to notify news mailbox creation ? */
r = mboxlist_createmailbox(mailboxname, 0, NULL, 0,
newsmaster, newsmaster_authstate, 0, 0, 0);
/* XXX check body of message for useful MIME parts */
return r;
}
static int rmgroup(message_data_t *msg)
{
int r;
char *group;
char mailboxname[MAX_MAILBOX_BUFFER];
/* isolate newsgroup */
group = msg->control + 7; /* skip "rmgroup" */
while (Uisspace(*group)) group++;
snprintf(mailboxname, sizeof(mailboxname), "%s%.*s",
newsprefix, (int) strcspn(group, " \t\r\n"), group);
/* skip mailboxes that we don't serve as newsgroups */
if (!is_newsgroup(mailboxname)) r = IMAP_MAILBOX_NONEXISTENT;
/* XXX should we delete right away, or wait until empty? */
if (!r) r = mboxlist_deletemailbox(mailboxname, 0,
newsmaster, newsmaster_authstate,
1, 0, 0);
if (!r) sync_log_mailbox(mailboxname);
return r;
}
static int mvgroup(message_data_t *msg)
{
int r;
size_t len;
char *group;
char oldmailboxname[MAX_MAILBOX_BUFFER];
char newmailboxname[MAX_MAILBOX_BUFFER];
/* isolate old newsgroup */
group = msg->control + 7; /* skip "mvgroup" */
while (Uisspace(*group)) group++;
len = strcspn(group, " \t\r\n");
snprintf(oldmailboxname, sizeof(oldmailboxname), "%s%.*s",
newsprefix, (int)len, group);
/* isolate new newsgroup */
group += len; /* skip old newsgroup */
while (Uisspace(*group)) group++;
len = strcspn(group, " \t\r\n");
snprintf(newmailboxname, sizeof(newmailboxname), "%s%.*s",
newsprefix, (int)len, group);
r = mboxlist_renamemailbox(oldmailboxname, newmailboxname, NULL, 0,
newsmaster, newsmaster_authstate, 0, 0);
/* XXX check body of message for useful MIME parts */
if (!r) sync_log_mailbox_double(oldmailboxname, newmailboxname);
return r;
}
/*
* mailbox_exchange() callback function to delete cancelled articles
*/
static unsigned expunge_cancelled(struct mailbox *mailbox __attribute__((unused)),
struct index_record *record,
void *rock)
{
/* only expunge the UID that we obtained from the msgid */
return (record->uid == *((unsigned long *) rock));
}
/*
* duplicate_find() callback function to cancel articles
*/
static int cancel_cb(const duplicate_t *dkey,
time_t mark __attribute__((unused)),
unsigned long uid,
void *rock)
{
struct mailbox *mailbox = NULL;
/* make sure its a message in a mailbox that we're serving via NNTP */
if (is_newsgroup(dkey->to)) {
int r;
r = mailbox_open_iwl(dkey->to, &mailbox);
if (!r &&
!(cyrus_acl_myrights(newsmaster_authstate, mailbox->acl) & ACL_DELETEMSG))
r = IMAP_PERMISSION_DENIED;
if (!r) r = mailbox_expunge(mailbox, expunge_cancelled, &uid, NULL, 0);
mailbox_close(&mailbox);
/* if we failed, pass the return code back in the rock */
if (r) *((int *) rock) = r;
}
return 0;
}
static int cancel(message_data_t *msg)
{
int r = 0;
char *msgid, *p;
/* isolate msgid */
msgid = strchr(msg->control, '<');
p = strrchr(msgid, '>') + 1;
*p = '\0';
/* find and expunge the message from all mailboxes */
duplicate_find(msgid, cancel_cb, &r);
/* store msgid of cancelled message for IHAVE/CHECK/TAKETHIS
* (in case we haven't received the message yet)
*/
duplicate_key_t dkey = {msgid, "", ""};
duplicate_mark(&dkey, 0, time(NULL));
return r;
}
#endif
/* strip any post addresses from a header body.
* returns 1 if a nonpost address was found, 0 otherwise.
*/
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
;
/* we didn't add this header, so leave it */
postlen
=
strlen
(
newspostuser
);
for
(
p
=
body
;;
p
+=
n
)
{
end
=
p
;
/* skip whitespace */
while
(
p
&&
*
p
&&
(
Uisspace
(
*
p
)
||
*
p
==
','
))
p
++
;
if
(
!
p
||
!*
p
)
break
;
/* find end of address */
n
=
strcspn
(
p
,
",
\t\r\n
"
);
if
((
n
>
postlen
+
1
)
&&
/* +1 for '+' */
!
strncmp
(
p
,
newspostuser
,
postlen
)
&&
p
[
postlen
]
==
'+'
)
{
/* found a post address. since we always add the post
* addresses to the end of the header, truncate it right here.
*/
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
;
/* parse the peer */
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'
;
}
/* check path to see if this message came through our peer */
len
=
strlen
(
host
);
path
=
msg
->
path
;
while
(
path
&&
(
s
=
strchr
(
path
,
'!'
)))
{
if
((
s
-
path
)
==
len
&&
!
strncmp
(
path
,
host
,
len
))
{
return
;
}
path
=
s
+
1
;
}
/* check newsgroups against wildmat to see if we should feed it */
if
(
wild
&&
*
wild
)
{
wmat
=
split_wildmats
(
wild
);
feed
=
0
;
for
(
n
=
0
;
n
<
msg
->
rcpt
.
count
;
n
++
)
{
/* see if the newsgroup matches one of our wildmats */
w
=
wmat
;
while
(
w
->
pat
&&
wildmat
(
msg
->
rcpt
.
data
[
n
],
w
->
pat
)
!=
1
)
{
w
++
;
}
if
(
w
->
pat
)
{
/* we have a match, check to see what kind of match */
if
(
!
w
->
not
)
{
/* positive match, ok to feed, keep checking */
feed
=
1
;
}
else
if
(
w
->
not
<
0
)
{
/* absolute negative match, do not feed */
feed
=
0
;
break
;
}
else
{
/* negative match, keep checking */
}
}
else
{
/* no match, keep checking */
}
}
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
);
/* read the initial greeting */
if
(
!
prot_fgets
(
buf
,
sizeof
(
buf
),
pin
)
||
strncmp
(
"200"
,
buf
,
3
))
{
syslog
(
LOG_ERR
,
"peer doesn't allow posting"
);
goto
quit
;
}
if
(
user
)
{
/* change to reader mode - not always necessary, so ignore result */
prot_printf
(
pout
,
"MODE READER
\r\n
"
);
prot_fgets
(
buf
,
sizeof
(
buf
),
pin
);
if
(
*
user
)
{
/* authenticate to peer */
/* XXX this should be modified to support SASL and STARTTLS */
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
))
{
/* password required */
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
))
{
/* auth failed */
syslog
(
LOG_ERR
,
"authentication failed"
);
goto
quit
;
}
}
/* tell the peer we want to post */
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
{
/* tell the peer about our new article */
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
;
}
}
/* send the article */
rewind
(
msg
->
f
);
while
(
fgets
(
buf
,
sizeof
(
buf
),
msg
->
f
))
{
if
(
!
body
&&
buf
[
0
]
==
'\r'
&&
buf
[
1
]
==
'\n'
)
{
/* blank line between header and body */
body
=
1
;
}
skip
=
0
;
if
(
!
body
)
{
if
(
!
strncasecmp
(
buf
,
"Reply-To:"
,
9
))
{
/* strip any post addresses, skip if becomes empty */
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
));
}
/* Protect against messages not ending in CRLF */
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
);
/* Flush the incoming buffer */
prot_NONBLOCK
(
pin
);
prot_fill
(
pin
);
/* close/free socket & prot layer */
close
(
sock
);
prot_free
(
pin
);
prot_free
(
pout
);
return
;
}
static
void
news2mail
(
message_data_t
*
msg
)
{
struct
buf
attrib
=
BUF_INITIALIZER
;
int
n
,
r
;
FILE
*
sm
;
static
strarray_t
smbuf
=
STRARRAY_INITIALIZER
;
static
int
smbuf_basic_count
;
int
sm_stat
;
pid_t
sm_pid
;
char
buf
[
4096
],
to
[
1024
]
=
""
;
if
(
!
smbuf
.
count
)
{
strarray_append
(
&
smbuf
,
"sendmail"
);
strarray_append
(
&
smbuf
,
"-i"
);
/* ignore dots */
strarray_append
(
&
smbuf
,
"-f"
);
strarray_append
(
&
smbuf
,
"<>"
);
strarray_append
(
&
smbuf
,
"--"
);
smbuf_basic_count
=
smbuf
.
count
;
}
for
(
n
=
0
;
n
<
msg
->
rcpt
.
count
;
n
++
)
{
/* see if we want to send this to a mailing list */
buf_free
(
&
attrib
);
r
=
annotatemore_lookup
(
msg
->
rcpt
.
data
[
n
],
"/vendor/cmu/cyrus-imapd/news2mail"
,
""
,
&
attrib
);
if
(
r
)
continue
;
/* add the email address to our argv[] and to our To: header */
if
(
attrib
.
s
)
{
strarray_append
(
&
smbuf
,
buf_cstring
(
&
attrib
));
if
(
to
[
0
])
strlcat
(
to
,
", "
,
sizeof
(
to
));
strlcat
(
to
,
buf_cstring
(
&
attrib
),
sizeof
(
to
));
}
}
buf_free
(
&
attrib
);
/* send the message */
if
(
smbuf
.
count
>
smbuf_basic_count
)
{
sm_pid
=
open_sendmail
((
const
char
**
)
smbuf
.
data
,
&
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'
)
{
/* blank line between header and body */
body
=
1
;
/* insert a To: header if the message doesn't have one */
if
(
!
found_to
)
fprintf
(
sm
,
"To: %s
\r\n
"
,
to
);
}
skip
=
0
;
if
(
!
body
)
{
/* munge various news-specific headers */
if
(
!
strncasecmp
(
buf
,
"Newsgroups:"
,
11
))
{
/* rename Newsgroups: to X-Newsgroups: */
fprintf
(
sm
,
"X-"
);
}
else
if
(
!
strncasecmp
(
buf
,
"Xref:"
,
5
)
||
!
strncasecmp
(
buf
,
"Path:"
,
5
)
||
!
strncasecmp
(
buf
,
"NNTP-Posting-"
,
13
))
{
/* skip these (for now) */
skip
=
1
;
}
else
if
(
!
strncasecmp
(
buf
,
"To:"
,
3
))
{
/* insert our mailing list RCPTs first, and then
fold the header to accomodate the original RCPTs */
fprintf
(
sm
,
"To: %s,
\r\n
"
,
to
);
/* overwrite the original "To:" with spaces */
memset
(
buf
,
' '
,
3
);
found_to
=
1
;
}
else
if
(
!
strncasecmp
(
buf
,
"Reply-To:"
,
9
))
{
/* strip any post addresses, skip if becomes empty */
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
));
}
/* Protect against messages not ending in CRLF */
if
(
buf
[
strlen
(
buf
)
-1
]
!=
'\n'
)
fprintf
(
sm
,
"
\r\n
"
);
fclose
(
sm
);
while
(
waitpid
(
sm_pid
,
&
sm_stat
,
0
)
<
0
);
if
(
sm_stat
)
/* sendmail exit value */
syslog
(
LOG_ERR
,
"news2mail failed: %s"
,
sendmail_errstr
(
sm_stat
));
}
/* free the RCPTs */
strarray_truncate
(
&
smbuf
,
smbuf_basic_count
);
}
return
;
}
static
void
cmd_post
(
char
*
msgid
,
int
mode
)
{
char
*
mboxname
;
FILE
*
f
=
NULL
;
message_data_t
*
msg
;
int
r
=
0
;
/* check if we want this article */
if
(
msgid
&&
my_find_msgid
(
msgid
,
&
mboxname
,
NULL
))
{
/* already have it */
syslog
(
LOG_INFO
,
"dupelim: news article id %s already present in mailbox %s"
,
msgid
,
mboxname
);
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
;
}
}
/* get a spool file (if needed) */
if
(
!
r
)
{
f
=
tmpfile
();
if
(
!
f
)
r
=
IMAP_IOERROR
;
}
if
(
f
)
{
msg_new
(
&
msg
);
/* spool the article */
r
=
savemsg
(
msg
,
f
);
/* deliver the article */
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 0
/* XXX Need to review control message auth/authz and implementation */
/* process control messages */
if (msg->control && !config_mupdate_server) {
int r1 = 0;
/* XXX check PGP signature */
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);
}
}
#endif
if
(
msg
->
id
)
{
const
char
*
peers
=
config_getstring
(
IMAPOPT_NEWSPEER
);
/* send the article upstream */
if
(
peers
)
{
char
*
tmpbuf
,
*
cur_peer
,
*
next_peer
;
/* make a working copy of the peers */
cur_peer
=
tmpbuf
=
xstrdup
(
peers
);
while
(
cur_peer
)
{
/* eat any leading whitespace */
while
(
Uisspace
(
*
cur_peer
))
cur_peer
++
;
/* find end of peer */
if
((
next_peer
=
strchr
(
cur_peer
,
' '
))
||
(
next_peer
=
strchr
(
cur_peer
,
'\t'
)))
*
next_peer
++
=
'\0'
;
/* feed the article to this peer */
feedpeer
(
cur_peer
,
msg
);
/* move to next peer */
cur_peer
=
next_peer
;
}
free
(
tmpbuf
);
}
/* gateway news to mail */
news2mail
(
msg
);
}
}
msg_free
(
msg
);
/* does fclose() */
if
(
stage
)
append_removestage
(
stage
);
stage
=
NULL
;
}
else
{
/* flush the article from the stream */
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
"
,
"TLS is already active"
);
return
;
}
if
(
nntp_authstate
)
{
prot_printf
(
nntp_out
,
"502 %s
\r\n
"
,
"Already authenticated"
);
return
;
}
/* SASL and openssl have different ideas about whether ssf is signed */
layerp
=
(
int
*
)
&
ssf
;
result
=
tls_init_serverengine
(
"nntp"
,
5
,
/* depth to verify */
!
nntps
,
/* can client auth? */
!
nntps
);
/* TLS only? */
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"
);
/* must flush our buffers before starting tls */
prot_flush
(
nntp_out
);
}
result
=
tls_start_servertls
(
0
,
/* read */
1
,
/* write */
nntps
?
180
:
nntp_timeout
,
layerp
,
&
auth_id
,
&
tls_conn
);
/* if error */
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
;
}
/* tell SASL about the negotiated layer */
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
);
/* tell the prot layer about our new layers */
prot_settls
(
nntp_in
,
tls_conn
);
prot_settls
(
nntp_out
,
tls_conn
);
nntp_starttls_done
=
1
;
/* close any selected group */
if
(
group_state
)
index_close
(
&
group_state
);
if
(
backend_current
)
{
proxy_downserver
(
backend_current
);
backend_current
=
NULL
;
}
}
#else
static
void
cmd_starttls
(
int
nntps
__attribute__
((
unused
)))
{
/* XXX should never get here */
fatal
(
"cmd_starttls() called, but no OpenSSL"
,
EC_SOFTWARE
);
}
#endif
/* HAVE_SSL */
static
struct
wildmat
*
split_wildmats
(
char
*
str
)
{
const
char
*
prefix
;
char
pattern
[
MAX_MAILBOX_BUFFER
]
=
""
,
*
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
);
/*
* split the list of wildmats
*
* we split them right to left because this is the order in which
* we want to test them (per RFC3977 section 4.2)
*/
do
{
if
((
c
=
strrchr
(
str
,
','
)))
*
c
++
=
'\0'
;
else
c
=
str
;
if
(
!
(
n
%
10
))
/* alloc some more */
wild
=
xrealloc
(
wild
,
(
n
+
11
)
*
sizeof
(
struct
wildmat
));
if
(
*
c
==
'!'
)
wild
[
n
].
not
=
1
;
/* not */
else
if
(
*
c
==
'@'
)
wild
[
n
].
not
=
-1
;
/* absolute not (feeding) */
else
wild
[
n
].
not
=
0
;
strncpy
(
p
,
wild
[
n
].
not
?
c
+
1
:
c
,
pattern
+
sizeof
(
pattern
)
-
p
);
pattern
[
sizeof
(
pattern
)
-1
]
=
'\0'
;
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
);
}
File Metadata
Details
Attached
Mime Type
text/x-c
Expires
Fri, Apr 24, 2:14 PM (1 w, 3 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18847208
Default Alt Text
nntpd.c (107 KB)
Attached To
Mode
R111 cyrus-imapd
Attached
Detach File
Event Timeline