1 /* Connection and data transport handling */
13 #include "cache/cache.h"
14 #include "intl/gettext/libintl.h"
15 #include "main/module.h"
16 #include "network/connection.h"
17 #include "network/socket.h"
18 #include "protocol/nntp/codes.h"
19 #include "protocol/nntp/connection.h"
20 #include "protocol/nntp/nntp.h"
21 #include "protocol/nntp/response.h"
22 #include "protocol/protocol.h"
23 #include "protocol/uri.h"
24 #include "util/memory.h"
25 #include "util/string.h"
28 /* The official color for this planet is green,
29 * which grows in pockets of them people willing to scheme. --delasoul */
31 static void nntp_send_command(struct connection
*conn
);
34 /* Connection setup: */
36 /* Resolves the target by looking at the part of the URI _after_ last '/' */
37 static enum nntp_target
38 get_nntp_target(unsigned char *data
, int datalen
)
40 enum nntp_target target
= NNTP_TARGET_ARTICLE_NUMBER
;
43 /* List groups on the server if there is nothing specified */
45 return NNTP_TARGET_GROUPS
;
47 /* Check for <message-id> */
48 if (memchr(data
, '@', datalen
))
49 return NNTP_TARGET_MESSAGE_ID
;
51 /* Check for <article-number> or <start-number>-<end-number> */
52 for (pos
= 0; pos
< datalen
; pos
++) {
53 if (!isdigit(data
[pos
])) {
54 /* Assume <group> if it is not a range dash
55 * or a range dash was already detected */
57 || target
== NNTP_TARGET_ARTICLE_RANGE
)
58 return NNTP_TARGET_GROUP
;
60 target
= NNTP_TARGET_ARTICLE_RANGE
;
64 /* If the scanning saw only numberical char and possibly one '-' it
65 * defaults to assume the URI part describes article number or range.
66 * This rules out numerical only group names. */
70 /* Init article range and check that both <start-number> and <end-number> are
71 * non empty and valid numbers and that the range is valid. */
73 init_nntp_article_range(struct nntp_connection_info
*nntp
,
74 unsigned char *data
, int datalen
)
76 long start_number
, end_number
;
80 start_number
= strtol(data
, (char **) &end
, 10);
81 if (errno
|| !end
|| *end
!= '-' || start_number
< 0)
84 end_number
= strtol(end
+ 1, (char **) &end
, 10);
85 if (errno
|| end
!= data
+ datalen
|| end_number
< 0)
88 nntp
->current_article
= start_number
;
89 nntp
->end_article
= end_number
;
91 nntp
->message
.source
= data
;
92 nntp
->message
.length
= datalen
;
94 return start_number
<= end_number
? 1 : 0;
97 static struct nntp_connection_info
*
98 init_nntp_connection_info(struct connection
*conn
)
100 struct uri
*uri
= conn
->uri
;
101 struct nntp_connection_info
*nntp
;
102 unsigned char *groupend
;
106 nntp
= mem_calloc(1, sizeof(*nntp
));
108 abort_connection(conn
, S_OUT_OF_MEM
);
114 set_string_magic(&nntp
->group
);
115 set_string_magic(&nntp
->message
);
118 datalen
= uri
->datalen
;
120 /* Check for <group>/ */
121 groupend
= memchr(data
, '/', datalen
);
123 int grouplen
= groupend
- data
;
125 nntp
->group
.source
= data
;
126 nntp
->group
.length
= grouplen
;
128 datalen
-= grouplen
+ 1;
129 data
+= grouplen
+ 1;
131 /* Nothing after last '/'? */
133 nntp
->target
= NNTP_TARGET_GROUP
;
138 nntp
->target
= get_nntp_target(data
, datalen
);
140 switch (nntp
->target
) {
141 case NNTP_TARGET_ARTICLE_RANGE
:
142 if (init_nntp_article_range(nntp
, data
, datalen
))
145 /* FIXME: Special S_NNTP_BAD_RANGE */
146 abort_connection(conn
, S_BAD_URL
);
149 case NNTP_TARGET_ARTICLE_NUMBER
:
150 case NNTP_TARGET_MESSAGE_ID
:
151 case NNTP_TARGET_GROUP_MESSAGE_ID
:
152 nntp
->message
.source
= data
;
153 nntp
->message
.length
= datalen
;
156 case NNTP_TARGET_GROUP
:
157 /* Map nntp://<server>/<group> to nntp://<server>/<group>/ so
158 * we get only one cache entry with content. */
160 enum connection_state state
= S_OK
;
162 conn
->cached
= get_cache_entry(conn
->uri
);
164 || !redirect_cache(conn
->cached
, "/", 0, 0))
165 state
= S_OUT_OF_MEM
;
167 abort_connection(conn
, state
);
171 /* Reject nntp://<server>/<group>/<group> */
172 if (nntp
->group
.source
) {
173 abort_connection(conn
, S_BAD_URL
);
177 nntp
->group
.source
= data
;
178 nntp
->group
.length
= datalen
;
181 case NNTP_TARGET_GROUPS
:
182 case NNTP_TARGET_QUIT
:
190 /* Connection teardown: */
193 nntp_quit(struct connection
*conn
)
195 struct nntp_connection_info
*info
;
197 info
= mem_calloc(1, sizeof(*info
));
199 abort_connection(conn
, S_OUT_OF_MEM
);
203 info
->target
= NNTP_TARGET_QUIT
;
207 nntp_send_command(conn
);
211 nntp_end_request(struct connection
*conn
, enum connection_state state
)
213 struct nntp_connection_info
*nntp
= conn
->info
;
215 if (nntp
->target
== NNTP_TARGET_QUIT
) {
216 abort_connection(conn
, state
);
222 normalize_cache_entry(conn
->cached
, conn
->from
);
224 } else if (state
== S_OUT_OF_MEM
) {
225 /* FIXME: Clear the socket buffers before ending so the one
226 * grabing the keepalive connection will be able to go on. */
229 set_connection_state(conn
, state
);
230 add_keepalive_connection(conn
, NNTP_KEEPALIVE_TIMEOUT
, nntp_quit
);
234 /* Reponse receiving: */
237 read_nntp_data(struct socket
*socket
, struct read_buffer
*rb
)
239 struct connection
*conn
= socket
->conn
;
241 if (socket
->state
== SOCKET_CLOSED
) {
242 nntp_end_request(conn
, S_OK
);
246 switch (read_nntp_response_data(conn
, rb
)) {
248 nntp_send_command(conn
);
252 nntp_end_request(conn
, S_OUT_OF_MEM
);
257 read_from_socket(conn
->socket
, rb
, S_TRANS
, read_nntp_data
);
261 /* Translate NNTP code to the internal connection state. */
262 static enum connection_state
263 get_nntp_connection_state(enum nntp_code code
)
266 case NNTP_CODE_400_GOODBYE
: return S_NNTP_SERVER_HANG_UP
;
267 case NNTP_CODE_411_GROUP_UNKNOWN
: return S_NNTP_GROUP_UNKNOWN
;
268 case NNTP_CODE_423_ARTICLE_NONUMBER
: return S_NNTP_ARTICLE_UNKNOWN
;
269 case NNTP_CODE_430_ARTICLE_NOID
: return S_NNTP_ARTICLE_UNKNOWN
;
270 case NNTP_CODE_436_ARTICLE_TRANSFER
: return S_NNTP_TRANSFER_ERROR
;
271 case NNTP_CODE_480_AUTH_REQUIRED
: return S_NNTP_AUTH_REQUIRED
;
272 case NNTP_CODE_502_ACCESS_DENIED
: return S_NNTP_ACCESS_DENIED
;
273 case NNTP_CODE_503_PROGRAM_FAULT
: return S_NNTP_SERVER_ERROR
;
275 case NNTP_CODE_412_GROUP_UNSET
:
276 case NNTP_CODE_420_ARTICLE_UNSET
:
277 case NNTP_CODE_421_ARTICLE_NONEXT
:
278 case NNTP_CODE_422_ARTICLE_NOPREV
:
279 case NNTP_CODE_435_ARTICLE_NOSEND
:
280 case NNTP_CODE_437_ARTICLE_REJECTED
:
281 case NNTP_CODE_440_POSTING_DENIED
:
282 case NNTP_CODE_441_POSTING_FAILED
:
283 case NNTP_CODE_482_AUTH_REJECTED
:
284 case NNTP_CODE_580_AUTH_FAILED
:
285 case NNTP_CODE_500_COMMAND_UNKNOWN
:
286 case NNTP_CODE_501_COMMAND_SYNTAX
:
288 /* Notice and error codes for stuff which is either not
289 * supported or which is not supposed to happen. */
295 nntp_got_response(struct socket
*socket
, struct read_buffer
*rb
)
297 struct connection
*conn
= socket
->conn
;
298 struct nntp_connection_info
*nntp
= conn
->info
;
300 if (socket
->state
== SOCKET_CLOSED
) {
301 nntp_end_request(conn
, S_OK
);
305 nntp
->code
= get_nntp_response_code(conn
, rb
);
307 switch (nntp
->code
) {
309 read_from_socket(conn
->socket
, rb
, S_TRANS
, nntp_got_response
);
312 case NNTP_CODE_INVALID
:
313 nntp_end_request(conn
, S_NNTP_ERROR
);
316 case NNTP_CODE_200_HELLO
:
317 case NNTP_CODE_201_HELLO_NOPOST
:
318 case NNTP_CODE_211_GROUP_SELECTED
:
319 nntp_send_command(conn
);
322 case NNTP_CODE_205_GOODBYE
:
323 nntp_end_request(conn
, S_OK
);
326 case NNTP_CODE_215_FOLLOW_GROUPS
:
327 case NNTP_CODE_220_FOLLOW_ARTICLE
:
328 case NNTP_CODE_221_FOLLOW_HEAD
:
329 case NNTP_CODE_222_FOLLOW_BODY
:
330 case NNTP_CODE_223_FOLLOW_NOTEXT
:
331 case NNTP_CODE_224_FOLLOW_XOVER
:
332 case NNTP_CODE_230_FOLLOW_NEWNEWS
:
333 case NNTP_CODE_231_FOLLOW_NEWGROUPS
:
334 read_nntp_data(socket
, rb
);
337 case NNTP_CODE_500_COMMAND_UNKNOWN
:
338 if (nntp
->command
== NNTP_COMMAND_LIST_ARTICLES
339 && !nntp
->xover_unsupported
) {
340 nntp
->xover_unsupported
= 1;
341 nntp_send_command(conn
);
346 /* FIXME: Handle (error) response codes */
347 nntp_end_request(conn
, get_nntp_connection_state(nntp
->code
));
353 nntp_get_response(struct socket
*socket
)
355 struct connection
*conn
= socket
->conn
;
356 struct read_buffer
*rb
= alloc_read_buffer(conn
->socket
);
360 socket
->state
= SOCKET_END_ONCLOSE
;
361 read_from_socket(conn
->socket
, rb
, conn
->state
, nntp_got_response
);
365 /* Command sending: */
367 /* Figure out the next command by looking at the target and the previous
369 static enum nntp_command
370 get_nntp_command(struct nntp_connection_info
*nntp
)
372 /* The more logical approach of making the switch noodle use the target
373 * actually will make it bigger altho' assertions of invalid states
374 * would be easier. I chose to make it simpler for now. --jonas */
375 /* All valid states should return from within the outer switch. Anything
376 * else should break out to the function end and cause an internal
378 switch (nntp
->command
) {
379 case NNTP_COMMAND_NONE
:
380 switch (nntp
->target
) {
381 case NNTP_TARGET_GROUPS
:
382 return NNTP_COMMAND_LIST_NEWSGROUPS
;
384 case NNTP_TARGET_GROUP
:
385 case NNTP_TARGET_ARTICLE_NUMBER
:
386 case NNTP_TARGET_ARTICLE_RANGE
:
387 case NNTP_TARGET_GROUP_MESSAGE_ID
:
388 return NNTP_COMMAND_GROUP
;
390 case NNTP_TARGET_MESSAGE_ID
:
391 return NNTP_COMMAND_ARTICLE_MESSAGE_ID
;
393 case NNTP_TARGET_QUIT
:
394 return NNTP_COMMAND_QUIT
;
398 case NNTP_COMMAND_GROUP
:
399 switch (nntp
->target
) {
400 case NNTP_TARGET_GROUP
:
401 case NNTP_TARGET_ARTICLE_RANGE
:
402 return NNTP_COMMAND_LIST_ARTICLES
;
404 case NNTP_TARGET_ARTICLE_NUMBER
:
405 return NNTP_COMMAND_ARTICLE_NUMBER
;
407 case NNTP_TARGET_GROUP_MESSAGE_ID
:
408 return NNTP_COMMAND_ARTICLE_MESSAGE_ID
;
410 case NNTP_TARGET_MESSAGE_ID
:
411 case NNTP_TARGET_QUIT
:
412 case NNTP_TARGET_GROUPS
:
417 case NNTP_COMMAND_LIST_ARTICLES
:
418 if (nntp
->target
!= NNTP_TARGET_GROUP
419 && nntp
->target
!= NNTP_TARGET_ARTICLE_RANGE
)
422 /* Are there more articles to list? */
423 if (nntp
->xover_unsupported
424 && nntp
->current_article
<= nntp
->end_article
)
425 return NNTP_COMMAND_LIST_ARTICLES
;
427 return NNTP_COMMAND_NONE
;
429 case NNTP_COMMAND_ARTICLE_NUMBER
:
430 if (nntp
->target
== NNTP_TARGET_ARTICLE_NUMBER
)
431 return NNTP_COMMAND_NONE
;
433 return NNTP_COMMAND_NONE
;
435 case NNTP_COMMAND_ARTICLE_MESSAGE_ID
:
436 case NNTP_COMMAND_LIST_NEWSGROUPS
:
437 case NNTP_COMMAND_QUIT
:
438 /* FIXME: assert()? --jonas */
439 return NNTP_COMMAND_NONE
;
442 INTERNAL("Bad command %d handling %d", nntp
->target
, nntp
->command
);
444 return NNTP_COMMAND_NONE
;
447 /* Command lines shall not exceed 512 characters in length, counting all
448 * characters including spaces, separators, punctuation, and the trailing CR-LF
449 * (Carriage Return - Line Feed) pair. */
450 #define NNTP_MAX_COMMAND_LENGTH 512
452 /* FIXME: For the beauty maybe %.*s could be used. --jonas */
454 add_nntp_command_to_string(struct string
*req
, struct nntp_connection_info
*nntp
)
456 switch (nntp
->command
) {
457 case NNTP_COMMAND_GROUP
:
458 add_to_string(req
, "GROUP ");
459 add_string_to_string(req
, &nntp
->group
);
462 case NNTP_COMMAND_ARTICLE_NUMBER
:
463 add_to_string(req
, "ARTICLE ");
464 add_string_to_string(req
, &nntp
->message
);
467 case NNTP_COMMAND_ARTICLE_MESSAGE_ID
:
468 add_to_string(req
, "ARTICLE <");
469 add_string_to_string(req
, &nntp
->message
);
470 add_char_to_string(req
, '>');
473 case NNTP_COMMAND_LIST_ARTICLES
:
474 if (!nntp
->xover_unsupported
) {
475 int first
= nntp
->current_article
++;
476 int last
= nntp
->end_article
;
478 add_format_to_string(req
, "XOVER %d-%d", first
, last
);
482 assert(nntp
->current_article
<= nntp
->end_article
);
484 add_format_to_string(req
, "HEAD %ld", nntp
->current_article
++);
487 case NNTP_COMMAND_QUIT
:
488 add_to_string(req
, "QUIT");
491 case NNTP_COMMAND_LIST_NEWSGROUPS
:
492 add_to_string(req
, "LIST NEWSGROUPS");
495 case NNTP_COMMAND_NONE
:
496 INTERNAL("Trying to add 'no' command.");
500 add_crlf_to_string(req
);
504 nntp_send_command(struct connection
*conn
)
506 struct nntp_connection_info
*nntp
= conn
->info
;
509 nntp
->command
= get_nntp_command(nntp
);
511 if (nntp
->command
== NNTP_COMMAND_NONE
) {
512 nntp_end_request(conn
, S_OK
);
516 if (!init_string(&req
)) {
517 nntp_end_request(conn
, S_OUT_OF_MEM
);
521 /* FIXME: Check non empty and < NNTP_MAX_COMMAND_LENGTH */
522 add_nntp_command_to_string(&req
, nntp
);
524 request_from_socket(conn
->socket
, req
.source
, req
.length
, S_SENT
,
525 SOCKET_END_ONCLOSE
, nntp_got_response
);
530 /* The ``nntp://'' and ``news:'' protocol handlers: */
533 nntp_protocol_handler(struct connection
*conn
)
535 if (!init_nntp_connection_info(conn
))
538 if (!has_keepalive_connection(conn
)) {
539 make_connection(conn
->socket
, conn
->uri
, nntp_get_response
,
540 conn
->cache_mode
>= CACHE_MODE_FORCE_RELOAD
);
542 nntp_send_command(conn
);
546 /* Note that while nntp:// URIs specify a unique location for the article
547 * resource, most NNTP servers currently on the Internet today are configured
548 * only to allow access from local clients, and thus nntp:// URIs do not
549 * designate globally accessible resources. Thus, the news: form of URI is
550 * preferred as a way of identifying news articles.
552 * Whenever dealing with news: URIs the <server> part is retrieved from either
553 * protocol.nntp.server or the NNTPSERVER environment variable. The news: URI
554 * is then redirected to a nntp:// URI using the <server> so that:
556 * news:<message-id> -> nntp://<server>/<message-id>
557 * news:<group> -> nntp://<server>/<group>
558 * news:<group>/<...> -> nntp://<server>/<group>/<...>
562 news_protocol_handler(struct connection
*conn
)
564 unsigned char *protocol
;
565 unsigned char *server
= get_nntp_server();
566 struct string location
;
568 if (!*server
) server
= getenv("NNTPSERVER");
569 if (!server
|| !*server
) {
570 abort_connection(conn
, S_NNTP_NEWS_SERVER
);
574 conn
->cached
= get_cache_entry(conn
->uri
);
575 if (!conn
->cached
|| !init_string(&location
)) {
576 abort_connection(conn
, S_OUT_OF_MEM
);
580 protocol
= conn
->uri
->protocol
== PROTOCOL_NEWS
? "nntp" : "nntps";
582 add_format_to_string(&location
, "%s://%s/", protocol
, server
);
583 add_uri_to_string(&location
, conn
->uri
, URI_DATA
);
585 redirect_cache(conn
->cached
, location
.source
, 0, 0);
587 done_string(&location
);
589 abort_connection(conn
, S_OK
);