Skip to content

Commit 02039da

Browse files
committed
Add Cancel-Lock support in innd, new docancels parameter
Implement Cancel-Lock in innd, based on RFC 8315 and libcanlock. Add new docancels parameter in inn.conf to define which types of cancels innd should honour. Deprecate "innd -C" in favour of this new parameter. see #47
1 parent 5ede521 commit 02039da

File tree

18 files changed

+331
-31
lines changed

18 files changed

+331
-31
lines changed

doc/pod/inn.conf.pod

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,54 @@ value if you set it, since IPv6 addresses contain colons.
202202

203203
This parameter has no effect when systemd socket activation is used.
204204

205+
=item I<docancels>
206+
207+
This parameter is intended for sites concerned about abuse of cancels, or that
208+
wish to enforce a mechanism to authenticate cancels. Unless rejected by the
209+
use of a filter hook, B<innd> always accepts and propagates cancel articles
210+
and supersede requests. However, actually processing such articles on the
211+
local news server depends on this parameter which can take the following
212+
values:
213+
214+
=over 4
215+
216+
=item C<require-auth>
217+
218+
Only articles originally protected by the Cancel-Lock authentication
219+
mechanism can be withdrawn by a valid authenticated cancel article or a valid
220+
authenticated supersede request. Withdrawals of articles not originally
221+
protected by Cancel-Lock will not be executed.
222+
223+
This is the default value if B<innd> knows how to authenticate cancels
224+
(that is to say if INN was built with Cancel-Lock support). Otherwise, the
225+
behaviour will be the same as C<none>.
226+
227+
=item C<auth>
228+
229+
Withdrawals of articles not originally protected by the Cancel-Lock
230+
authentication mechanism will always be executed. However, if the original
231+
article is protected, only a valid authenticated cancel article or a valid
232+
authenticated supersede request will permit withdrawing it. (If INN was not
233+
built with Cancel-Lock support, such protected articles won't be withdrawn.)
234+
235+
=item C<none>
236+
237+
Neither cancel articles nor supersede requests will be processed; no articles
238+
will be withdrawn.
239+
240+
This is the default value if B<innd> does not know how to authenticate cancels
241+
(that is to say if INN was not built with Cancel-Lock support) as it has no
242+
means to ensure that these withdrawal requests are legitimate.
243+
244+
=item C<all>
245+
246+
B<innd> will process all cancel articles and supersede requests, even if
247+
unauthenticated, forged or with bad authentication. You should be sure of
248+
what you are doing if you choose that value as any article can be withdrawn
249+
(even by someone who is not the author of the article).
250+
251+
=back
252+
205253
=item I<dontrejectfiltered>
206254

207255
Normally innd(8) rejects incoming articles when directed to do so by
@@ -362,8 +410,7 @@ conceal their identity and provided no security". This check is
362410
therefore not done as it is extremely easy to spoof.
363411

364412
In order not to actually process any cancel or supersedes messages,
365-
you can start B<innd> with the B<-C> flag, or add this flag to the
366-
I<innflags> parameter.
413+
you can set I<docancels> to C<none>.
367414

368415
=item I<verifygroups>
369416

doc/pod/innd.pod

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ innd - InterNetNews daemon
44

55
=head1 SYNOPSIS
66

7-
B<innd> [B<-aCdfNrsSu>] [B<-4> I<address>] [B<-6> I<address>] [B<-c> I<days>]
7+
B<innd> [B<-adfNrsSu>] [B<-4> I<address>] [B<-6> I<address>] [B<-c> I<days>]
88
[B<-H> I<count>] [B<-i> I<count>] [B<-l> I<size>] [B<-m> I<mode>]
99
[B<-n> I<flag>] [B<-o> I<count>] [B<-P> I<port>] [B<-t> I<timeout>]
1010
[B<-T> I<count>] [B<-X> I<seconds>]
@@ -114,13 +114,6 @@ specified by the C</remember/> line in expire.ctl(5). You'll have to
114114
wait that number of days before being able to inject again an article
115115
with the same previously rejected Message-ID.
116116

117-
=item B<-C>
118-
119-
This flag tells B<innd> to accept and propagate but not actually process
120-
cancel or supersedes messages. This is intended for sites concerned about
121-
abuse of cancels, or that wish to use another cancel mechanism with
122-
stronger authentication.
123-
124117
=item B<-d>, B<-f>
125118

126119
B<innd> normally puts itself into the background, points its standard

doc/pod/news.pod

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,20 @@ used for the new Cancel-Lock functionality.
2525

2626
=item *
2727

28+
The B<-C> flag given to B<innd> to disable the execution of cancels has been
29+
deprecated and is no longer taken into account (an error message will be
30+
present in your logs if B<innd> is started with it). Instead, a new parameter
31+
has been added in F<inn.conf> to tune the types of cancels B<innd> should
32+
process. If I<docancels> is set to C<require-auth>, which is the default
33+
if INN has Cancel-Lock support, only articles originally protected by the
34+
Cancel-Lock authentication mechanism can be withdrawn by a valid authenticated
35+
cancel article or a valid authenticated supersede request. Withdrawals of
36+
articles not originally protected by Cancel-Lock will not be executed.
37+
See inn.conf(5) for more details about the different values of the new
38+
I<docancels> parameter, and make sure to parameter it according to your needs.
39+
40+
=item *
41+
2842
B<filechan> is no longer shipped with INN; it was just a simple version of
2943
B<buffchan>. All calls to C<filechan> will be changed to C<buffchan -u>
3044
(for its unbuffered mode) in F<newsfeeds> by B<innupgrade>. If you have
@@ -85,10 +99,18 @@ and makehistory(8) man pages.
8599

86100
=item *
87101

88-
Julien Elie has implemented Cancel-Lock support in B<nnrpd>, based on S<RFC
89-
8315> and libcanlock. A new F<inn-secrets.conf> configuration file has been
90-
added in I<pathetc> wherein you can set the secrets to use for Cancel-Lock.
91-
See the inn-secrets.conf(5) man page for more details.
102+
Julien Elie has implemented Cancel-Lock support in B<innd> and B<nnrpd>,
103+
based on S<RFC 8315> and libcanlock. A new F<inn-secrets.conf> configuration
104+
file has been added in I<pathetc> wherein you can set the secrets to use for
105+
Cancel-Lock. See the inn-secrets.conf(5) man page for more details.
106+
107+
=item *
108+
109+
A new I<docancels> parameter has been added in F<inn.conf> to define which
110+
types of cancels B<innd> should process. The B<-C> flag given to B<innd>
111+
is deprecated in favour of that new parameter (you'll see in your logs the
112+
message C<innd -C flag has been deprecated and has no effect; use docancels in
113+
inn.conf> in case you're passing that flag to B<innd>).
92114

93115
=item *
94116

include/inn/innconf.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ struct innconf {
3636
unsigned long artcutoff; /* Max accepted article age */
3737
char *bindaddress; /* Which interface IP to bind to */
3838
char *bindaddress6; /* Which interface IPv6 to bind to */
39+
char *docancels; /* Which cancels to process */
3940
bool dontrejectfiltered; /* Don't reject filtered article? */
4041
unsigned long hiscachesize; /* Size of the history cache in kB */
4142
bool ignorenewsgroups; /* Propagate cmsgs by affected group? */

include/inn/libinn.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,19 @@ extern void xsignal_enable_masking(void);
8989
extern void xsignal_forked(void);
9090

9191

92-
/* Headers. */
92+
/*
93+
** HEADERS
94+
**
95+
** Do not test HAVE_CANLOCK. This relieves customers of /usr/include/inn from
96+
** the need to guess whether INN was built with Cancel-Lock support in order
97+
** to get a header that matches the installed libraries.
98+
*/
9399
extern bool gen_cancel_lock(const char *msgid, const char *username,
94100
char **canbuff);
95101
extern bool gen_cancel_key(const char *hdrcontrol, const char *hdrsupersedes,
96102
const char *username, char **canbuff);
103+
extern bool verify_cancel_key(const char *c_key_header,
104+
const char *c_lock_header);
97105
extern char *GenerateMessageID(char *domain);
98106
extern void InitializeMessageIDcclass(void);
99107
extern bool IsValidMessageID(const char *string, bool stripspaces,
@@ -103,6 +111,7 @@ extern bool IsValidHeaderBody(const char *string);
103111
extern bool IsValidHeaderField(const char *string);
104112
extern const char *skip_cfws(const char *p);
105113
extern const char *skip_fws(const char *p);
114+
extern char *spaced_words_without_cfws(const char *p);
106115
extern void HeaderCleanFrom(char *from);
107116
extern struct _DDHANDLE *DDstart(FILE *FromServer, FILE *ToServer);
108117
extern void DDcheck(struct _DDHANDLE *h, char *group);

innd/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ clean clobber distclean maintclean:
3232
## Compilation rules.
3333

3434
INNDLIBS = $(LIBSTORAGE) $(LIBHIST) $(LIBINN) $(STORAGE_LIBS) \
35-
$(SYSTEMD_LIBS) \
35+
$(SYSTEMD_LIBS) $(CANLOCK_LDFLAGS) $(CANLOCK_LIBS) \
3636
$(PERL_LIBS) $(PYTHON_LIBS) $(REGEX_LIBS) $(LIBS)
3737

3838
perl.o: perl.c ; $(CC) $(CFLAGS) $(PERL_CPPFLAGS) -c perl.c

innd/art.c

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1238,11 +1238,17 @@ void
12381238
ARTcancel(const ARTDATA *data, const char *MessageID, const bool Trusted)
12391239
{
12401240
char buff[SMBUF + 16];
1241+
const char *start, *end;
1242+
ARTHANDLE *art;
12411243
TOKEN token;
1244+
#if defined(HAVE_CANLOCK)
1245+
char *canlockhdr;
1246+
const HDRCONTENT *hc = data->HdrContent;
1247+
#endif
12421248
bool r;
12431249

12441250
TMRstart(TMR_ARTCNCL);
1245-
if (!DoCancels && !Trusted) {
1251+
if (strcasecmp(innconf->docancels, "none") == 0 && !Trusted) {
12461252
TMRstop(TMR_ARTCNCL);
12471253
return;
12481254
}
@@ -1254,10 +1260,76 @@ ARTcancel(const ARTDATA *data, const char *MessageID, const bool Trusted)
12541260
return;
12551261
}
12561262

1263+
/* No additional checks if cancels should all be executed. */
1264+
if (strcasecmp(innconf->docancels, "all") != 0 && !Trusted) {
1265+
/* Retrieve the Cancel-Lock header field of the article to be
1266+
* cancelled. If the original article is not found or has no
1267+
* Cancel-Lock header field, we cannot authenticate the cancel.
1268+
*
1269+
* It also means that we do not honour cancel requests for articles not
1270+
* yet received, even if they do not have a Cancel-Lock header field
1271+
* (but we don't know yet). */
1272+
if (!HISlookup(History, MessageID, NULL, NULL, NULL, &token)) {
1273+
TMRstop(TMR_ARTCNCL);
1274+
return;
1275+
}
1276+
if ((art = SMretrieve(token, RETR_HEAD)) == NULL) {
1277+
TMRstop(TMR_ARTCNCL);
1278+
return;
1279+
}
1280+
start = wire_findheader(art->data, art->len, "Cancel-Lock", true);
1281+
1282+
if (start == NULL
1283+
&& strcasecmp(innconf->docancels, "require-auth") == 0) {
1284+
SMfreearticle(art);
1285+
TMRstop(TMR_ARTCNCL);
1286+
return;
1287+
} else if (start != NULL) {
1288+
/* canlockhdr will store a copy of the Cancel-Lock header field
1289+
* body of the original article to be cancelled.
1290+
* end points to the final "\n" of the header field body. */
1291+
end = wire_endheader(start, art->data + art->len - 1);
1292+
1293+
if (end == NULL
1294+
&& strcasecmp(innconf->docancels, "require-auth") == 0) {
1295+
SMfreearticle(art);
1296+
TMRstop(TMR_ARTCNCL);
1297+
return;
1298+
} else if (end != NULL) {
1299+
r = false;
1300+
#if defined(HAVE_CANLOCK)
1301+
if (HDR(HDR__CANCEL_KEY) != NULL) {
1302+
canlockhdr = xstrndup(start, end - start);
1303+
1304+
/* Is the Cancel-Key header field legitimate? */
1305+
r = verify_cancel_key(HDR(HDR__CANCEL_KEY), canlockhdr);
1306+
1307+
free(canlockhdr);
1308+
}
1309+
#endif
1310+
SMfreearticle(art);
1311+
1312+
if (r == false) {
1313+
TMRstop(TMR_ARTCNCL);
1314+
return;
1315+
}
1316+
} else {
1317+
/* docancels is "auth", and there is no Cancel-Lock. */
1318+
SMfreearticle(art);
1319+
}
1320+
} else {
1321+
/* docancels is "auth", and there is no Cancel-Lock. */
1322+
SMfreearticle(art);
1323+
}
1324+
}
1325+
12571326
if (!HIScheck(History, MessageID)) {
12581327
/* Article hasn't arrived here, so write a fake entry using
12591328
* most of the information from the cancel message. */
1260-
if (innconf->verifycancels && !Trusted) {
1329+
if (innconf->verifycancels
1330+
&& (strcasecmp(innconf->docancels, "require-auth") == 0
1331+
|| strcasecmp(innconf->docancels, "auth") == 0)
1332+
&& !Trusted) {
12611333
TMRstop(TMR_ARTCNCL);
12621334
return;
12631335
}
@@ -2673,7 +2745,7 @@ ARTpost(CHANNEL *cp)
26732745
if (IsControl) {
26742746
ARTcontrol(data, HDR(HDR__CONTROL), cp);
26752747
}
2676-
if (DoCancels && HDR_FOUND(HDR__SUPERSEDES)) {
2748+
if (HDR_FOUND(HDR__SUPERSEDES)) {
26772749
if (IsValidMessageID(HDR(HDR__SUPERSEDES), true, laxmid))
26782750
ARTcancel(data, HDR(HDR__SUPERSEDES), false);
26792751
}

innd/innd.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ bool Debug = false;
2222
bool NNRPTracing = false;
2323
bool StreamingOff = false; /* default is we can stream */
2424
bool Tracing = false;
25-
bool DoCancels = true;
2625
char LogName[] = "SERVER";
2726
int ErrorCount = IO_ERROR_COUNT;
2827
OPERATINGMODE Mode = OMrunning;
@@ -147,7 +146,7 @@ const ARTHEADER ARTheaders[] = {
147146
ARTHEADERINIT("X-Canceled-By", HTstd),
148147
/* #define HDR__XCANCELEDBY 35 */
149148
ARTHEADERINIT("Cancel-Key", HTstd),
150-
/* #define HDR__CANCELKEY 36 */
149+
/* #define HDR__CANCEL_KEY 36 */
151150
ARTHEADERINIT("User-Agent", HTstd),
152151
/* #define HDR__USER_AGENT 37 */
153152
ARTHEADERINIT("X-Original-Message-ID", HTstd),
@@ -460,7 +459,8 @@ main(int ac, char *av[])
460459
innconf->artcutoff = strtoul(optarg, NULL, 10);
461460
break;
462461
case 'C':
463-
DoCancels = false;
462+
syslog(LOG_ERR, "innd -C flag has been deprecated and has no "
463+
"effect; use docancels in inn.conf");
464464
break;
465465
case 'd':
466466
Debug = true;

innd/innd.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ typedef struct _HDRCONTENT {
184184
#define HDR__XNEWSPOSTER 33
185185
#define HDR__XCANCELLEDBY 34
186186
#define HDR__XCANCELEDBY 35
187-
#define HDR__CANCELKEY 36
187+
#define HDR__CANCEL_KEY 36
188188
#define HDR__USER_AGENT 37
189189
#define HDR__X_ORIGINAL_MESSAGE_ID 38
190190
#define HDR__CANCEL_LOCK 39
@@ -643,7 +643,6 @@ extern const ARTHEADER ARTheaders[MAX_ARTHEADER];
643643
extern bool BufferedLogs;
644644
EXTERN bool AnyIncoming;
645645
extern bool Debug;
646-
extern bool DoCancels;
647646
extern bool laxmid;
648647
EXTERN bool ICDneedsetup;
649648
EXTERN bool NeedHeaders;

lib/canlock.c

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,4 +329,39 @@ gen_cancel_key(const char *hdrcontrol, const char *hdrsupersedes,
329329

330330
return gencankey;
331331
}
332+
333+
334+
/* Verify that a Cancel-Key header field body contains an element matching one
335+
** of those present in a Cancel-Lock header field body.
336+
** This function expects pointers to Cancel-Key and Cancel-Lock header field
337+
** bodies as arguments.
338+
**
339+
** Returns true if at least one c-key element matches.
340+
** Otherwise, false is returned, that is to say the cancel or supersede
341+
** request cannot be authenticated.
342+
*/
343+
bool
344+
verify_cancel_key(const char *c_key_header, const char *c_lock_header)
345+
{
346+
char *c_key_list, *c_lock_list;
347+
int status = -1;
348+
349+
if (c_key_header == NULL || c_lock_header == NULL)
350+
return false;
351+
352+
/* Rewrite header field bodies as space-separated lists of elements. */
353+
c_key_list = spaced_words_without_cfws(c_key_header);
354+
c_lock_list = spaced_words_without_cfws(c_lock_header);
355+
356+
if (c_key_list != NULL && c_lock_list != NULL) {
357+
/* Now call the function that actually does the check.
358+
* The status will be 0 if a match is found. */
359+
status = cl_verify_multi(NULL, c_key_list, c_lock_list);
360+
}
361+
362+
free(c_key_list);
363+
free(c_lock_list);
364+
365+
return (status == 0);
366+
}
332367
#endif /* HAVE_CANLOCK */

0 commit comments

Comments
 (0)