nntp.c
Go to the documentation of this file.
1 /* ========================================================================== */
2 /*! \file
3  * \brief Network News Transfer Protocol
4  *
5  * Copyright (c) 2012-2023 by the developers. See the LICENSE file for details.
6  *
7  * If nothing else is specified, functions return zero to indicate success
8  * and a negative value to indicate an error.
9  */
10 
11 
12 /* ========================================================================== */
13 /* Include headers */
14 
15 #include "posix.h" /* Include this first because of feature test macros */
16 
17 #include <ctype.h>
18 #include <stdarg.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 
23 #include "conf.h"
24 #include "config.h"
25 #include "compression.h"
26 #include "core.h"
27 #include "encoding.h"
28 #include "inet.h"
29 #include "log.h"
30 #include "main.h"
31 #include "nntp.h"
32 #include "tls.h"
33 
34 
35 /* ========================================================================== */
36 /*! \defgroup NNTP NNTP: Network News Transfer Protocol
37  *
38  * This transport subsystem should behave RFC 1951, RFC 977, RFC 2980, RFC 3977,
39  * RFC 4643 and RFC 6048 conformant.
40  *
41  * Secure NNTPS connections can be requested if TLS support is available.
42  *
43  * The AUTHINFO USER/PASS extension from RFC 4643 is supported if TLS support is
44  * available. The AUTHINFO SASL extension from RFC 4643 is not supported.
45  *
46  * Compression support is used according to RFC 8054.
47  * Compression is used if advertised by the server (with COMPRESS capability),
48  * the required local libraries (e.g. zlib for DEFLATE algorithm) are available
49  * and compression isn't disabled by configuration (CONF_COMPRESSION parameter).
50  *
51  * \note
52  * Authentication is always done before enabling compression.
53  *
54  * RFC 3977 allows non-ASCII encoded group names, but recommend not to use them.
55  * They are supported nevertheless by this transport driver
56  *
57  * \attention
58  * RFC 5536 conformant messages can't use Unicode group names.
59  * RFC 5536 mandates that the whole header must be ASCII encoded and forbids
60  * MIME encoded words as defined in RFC 2047 for the group identifiers inside
61  * the "Newsgroups" and "Followup-To" header fields.
62  *
63  * \note
64  * Unicode identifiers are ambigous by definition and can't simply be used as
65  * received without an additional normalization step.
66  * As long as there is no "canonical" normalization algorithm defined for the
67  * Usenet, it is in general a good idea to not use Unicode for identifiers at
68  * all.
69  */
70 /*! @{ */
71 
72 
73 /* ========================================================================== */
74 /* Constants */
75 
76 /*! \brief Message prefix for NNTP module */
77 #define MAIN_ERR_PREFIX "NNTP: "
78 
79 /*! \brief Maximum number of simultaneous NNTP connections */
80 #define NNTP_HANDLEMAX 1U
81 
82 /*! \brief Maximum line length (for command/response, not payload)
83  *
84  * Must be at least 512 characters.
85  * <br>
86  * RFC 977 : Maximum command line length is 512 characters
87  * <br>
88  * RFC 3977: Maximum length of first line of response is 512 characters
89  */
90 #define NNTP_LINELENGTHMAX (size_t) 512
91 
92 /*! \brief NNTP V2 maximum command argument length (according to RFC 3977) */
93 #define NNTP_ARGLENGTHMAX (size_t) 497
94 
95 /*! \name NNTP capabilities
96  *
97  * The flags can be bitwise ORed together.
98  */
99 /*! @{ */
100 #define NNTP_CAPA_MODE_READER 0x0001U
101 #define NNTP_CAPA_READER 0x0002U
102 #define NNTP_CAPA_LIST 0x0004U
103 #define NNTP_CAPA_LIST_MOTD 0x0008U
104 #define NNTP_CAPA_LIST_DISTRIB_PATS 0x0010U
105 #define NNTP_CAPA_LIST_SUBSCRIPTIONS 0x0020U
106 #define NNTP_CAPA_OVER 0x0040U
107 #define NNTP_CAPA_POST 0x0080U
108 #define NNTP_CAPA_AUTHINFO_USER 0x0100U
109 #define NNTP_CAPA_COMPRESS 0x0200U
110 #define NNTP_CAPA_MAXARTNUM 0x0400U
111 /*! @} */
112 
113 /*! \name Algorithms for NNTP compression extension
114  *
115  * The flags can be bitwise ORed together.
116  */
117 /*! @{ */
118 #define NNTP_COMPRESS_DEFLATE 0x0001U
119 /*! @} */
120 
121 
122 /* ========================================================================== */
123 /* Data types */
124 
125 /* NNTP commands */
126 enum nntp_cmd
127 {
128  NNTP_CMD_INIT, /* Dummy for reply to initial connect */
129  NNTP_CMD_QUIT,
130  NNTP_CMD_CAPABILITIES,
131  NNTP_CMD_MODE_READER,
132  NNTP_CMD_MAXARTNUM,
133  NNTP_CMD_COMPRESS,
134  NNTP_CMD_AUTHINFO_USER,
135  NNTP_CMD_AUTHINFO_PASS,
136  NNTP_CMD_LIST, /* Equal to LIST ACTIVE in NNTP V2 */
137  NNTP_CMD_LIST_NEWSGROUPS,
138  NNTP_CMD_LIST_MOTD,
139  NNTP_CMD_LIST_DISTRIB_PATS,
140  NNTP_CMD_LIST_SUBSCRIPTIONS,
141  NNTP_CMD_LIST_OVERVIEW_FMT,
142  NNTP_CMD_GROUP,
143  NNTP_CMD_OVER,
144  NNTP_CMD_ARTICLE_BY_MID,
145  NNTP_CMD_ARTICLE,
146  NNTP_CMD_HEAD,
147  NNTP_CMD_BODY,
148  NNTP_CMD_POST
149 };
150 
151 /* NNTP server handle */
152 struct nntp_server
153 {
154  unsigned int connected; /* Connection established flag */
155  int sd; /* Socket descriptor */
156  FILE* lfs; /* Logfile stream */
157  unsigned int version; /* Protocol version */
158  unsigned int capabilities; /* Capability flags */
159  size_t over_newsgroups; /* Index of Newsgroups header field */
160  unsigned int compress_algs; /* Compression algorithms */
161  int compress_active; /* Compression layer active flag */
162  void* compress_stream; /* Pointer to compressed data stream object */
163  int auth; /* Authentication algorithm */
164  nntp_anum_t maxartnum; /* Maximum article number from server */
165  char* distrib_pats; /* Distribution patterns */
166  size_t distrib_pats_len; /* Length of distribution patterns */
167 #if CFG_USE_TLS
168  void* eco; /* Encrypted connection object */
169  const char* user; /* User account */
170  const char* passwd; /* Password for user account */
171 # if CFG_USE_OPENSSL_API_1_1 && !CFG_USE_OPENSSL_API_3
172  void* peekbuf; /* Peek buffer for OpenSSL 1.1.x */
173  size_t peekbuf_len; /* Length of peek buffer for OpenSSL 1.1.x */
174 # endif /* CFG_USE_OPENSSL_API_1_1 && !CFG_USE_OPENSSL_API_3 */
175 #endif /* CFG_USE_TLS */
176 };
177 
178 /* NNTP server response */
179 struct nntp_response
180 {
181  unsigned int status; /* Complete status code */
182  unsigned char code1; /* Status code 1. digit */
183  unsigned char code2; /* Status code 2. digit */
184  unsigned char code3; /* Status code 3. digit */
185  char* msg; /* First line of response */
186  char* content; /* Content of response */
187  size_t lines; /* Number of lines in 'content' */
188  size_t bufsize; /* Buffer size for 'content' */
189 };
190 
191 
192 /* ========================================================================== */
193 /* Variables */
194 
195 /* Server state array */
196 static struct nntp_server server[NNTP_HANDLEMAX];
197 
198 
199 /* ========================================================================== */
200 /* Forward declarations */
201 
202 static int exec_auth(int);
203 static int refresh_capabilities(int);
204 static int refresh_overview_format(int);
205 
206 
207 /* ========================================================================== */
208 /* Watermark parser
209  *
210  * Max. 20 digits are supported for MAXARTNUM extension.
211  * RFC 3977 allows max. 16 digits.
212  *
213  * This function must correctly process leading zeros.
214  */
215 
216 static int parse_number(int c, char wm[16], int* wm_len, nntp_anum_t* n)
217 {
218  int res = 1; /* A result of 1 means "feed more data" */
219 
220  if(0x20 == c && *wm_len)
221  {
222  if(enc_convert_ascii_to_anum((core_anum_t*) n, wm, *wm_len))
223  {
224  PRINT_ERROR("Invalid water mark field");
225  res = -1;
226  }
227  else { res = 0; }
228  }
229  else
230  {
231  /* Verify that next character is a digit */
232  if(!(0x30 <= c && 0x39 >= c))
233  {
234  PRINT_ERROR("Invalid character in water mark field");
235  res = -1;
236  }
237  /* Check length limit */
238  if(16 <= *wm_len)
239  {
240  PRINT_ERROR("Water mark field too long");
241  res = -1;
242  }
243  wm[(*wm_len)++] = (char) c;
244  }
245 
246  return(res);
247 }
248 
249 
250 /* ========================================================================== */
251 /* Initialize server handle object */
252 
253 static int get_handle(int* handle)
254 {
255  int res = -1;
256  unsigned int i;
257 
258  /* Search for unused handle */
259  for(i = 0; i < NNTP_HANDLEMAX; ++i)
260  {
261  if(server[i].connected) continue;
262 
263  /* Found => Init handle */
264  server[i].sd = -1;
265  server[i].lfs = NULL;
266  server[i].version = 0; /* Unknown */
267  server[i].capabilities = 0;
268  server[i].over_newsgroups = 0; /* Not supported */
269  server[i].compress_algs = 0;
270  server[i].compress_active = 0;
271  server[i].compress_stream = NULL;
272  server[i].auth = 0;
273  server[i].maxartnum = 0;
274  server[i].distrib_pats = NULL;
275  server[i].distrib_pats_len = 0;
276 #if CFG_USE_TLS
277  server[i].eco = NULL;
278  server[i].user = NULL;
279  server[i].passwd = NULL;
280 # if CFG_USE_OPENSSL_API_1_1 && !CFG_USE_OPENSSL_API_3
281  server[i].peekbuf = NULL;
282  server[i].peekbuf_len = 0;
283 # endif /* CFG_USE_OPENSSL_API_1_1 && !CFG_USE_OPENSSL_API_3 */
284 #endif /* CFG_USE_TLS */
285 
286  *handle = (int) i;
287  res = 0;
288  break;
289  }
290 
291  return(res);
292 }
293 
294 
295 /* ========================================================================== */
296 /* Flush TX direction of transport layer */
297 
298 static int transport_flush(int handle, int terminate)
299 {
300  int res = 0;
301 
302 #if !CFG_CMPR_DISABLE
303  if(server[handle].compress_active)
304  {
305  if(!terminate) { res = cmpr_flush(server[handle].compress_stream); }
306  else { res = cmpr_terminate(server[handle].compress_stream); }
307  }
308 #endif /* !CFG_CMPR_DISABLE */
309 
310  return(res);
311 }
312 
313 
314 /* ========================================================================== */
315 /* Send data to server
316  *
317  * \attention
318  * The parameter \e flags must always be zero.
319  */
320 
321 static posix_ssize_t transport_send(int handle, const void* buf, size_t len,
322  int flags)
323 {
324  /* No flags are supported */
325  if(flags)
326  {
327  PRINT_ERROR("transport_send(): Called with invalid flags");
328  return(-1);
329  }
330 #if CFG_USE_TLS
331  if(NULL != server[handle].eco)
332  {
333  return(tls_send(server[handle].eco, buf, len));
334  }
335 #endif /* CFG_USE_TLS */
336  return(posix_send(server[handle].sd, buf, len, flags));
337 }
338 
339 
340 /* ========================================================================== */
341 /* Send data with optional compression */
342 
343 static posix_ssize_t srv_send(int handle, const void* buf, size_t len)
344 {
345 #if !CFG_CMPR_DISABLE
346  if(server[handle].compress_active)
347  {
348  return(cmpr_send(server[handle].compress_stream, buf, len));
349  }
350  else
351 #endif /* !CFG_CMPR_DISABLE */
352  {
353  return(transport_send(handle, buf, len, 0));
354  }
355 }
356 
357 
358 /* ========================================================================== */
359 /* Receive data from server
360  *
361  * \attention
362  * For the parameter \e flags , only \c POSIX_MSG_PEEK is supported.
363  */
364 
365 static posix_ssize_t transport_recv(int handle, void* buf, size_t len,
366  int flags)
367 {
368  posix_ssize_t res = -1;
369 #if CFG_USE_TLS
370  int peek = 0;
371 # if CFG_USE_OPENSSL_API_1_1 && !CFG_USE_OPENSSL_API_3
372  char* p;
373 # endif /* CFG_USE_OPENSSL_API_1_1 && !CFG_USE_OPENSSL_API_3 */
374 #endif /* CFG_USE_TLS */
375 
376  /* Check data length */
377  if(POSIX_SSIZE_MAX < len)
378  {
379  PRINT_ERROR("transport_recv(): Called with invalid data length");
380  return(-1);
381  }
382 
383  /* Only the 'MSG_PEEK' flag is supported */
384  if(~(POSIX_MSG_PEEK) & flags)
385  {
386  PRINT_ERROR("transport_recv(): Called with invalid flags");
387  return(-1);
388  }
389 
390 #if CFG_USE_TLS
391  if(NULL != server[handle].eco)
392  {
393  if(POSIX_MSG_PEEK & flags) { peek = 1; }
394 # if CFG_USE_OPENSSL_API_1_1 && !CFG_USE_OPENSSL_API_3
395  /*
396  * In OpenSSL 1.1.0 the peek flag handling no longer work via the
397  * function 'SSL_peek()'. OpenSSL spins instead of processing the
398  * (available) data from the socket.
399  *
400  * This is a workaround that uses it's own buffer inside the server
401  * handle. It is ugly to do this here, but every handle requires its
402  * own buffer that is associated with the connection object 'eco'.
403  *
404  * The workaround is no longer needed for OpenSSL API 3.
405  */
406  if(!peek && !server[handle].peekbuf_len)
407  {
408  res = tls_recv(server[handle].eco, buf, len, 0);
409  }
410  else
411  {
412  /* Check for peek request and empty peek buffer */
413  if(peek && !server[handle].peekbuf_len)
414  {
415  /* Resize peek buffer */
416  p = (char*) posix_realloc(server[handle].peekbuf, len);
417  if(NULL != p)
418  {
419  server[handle].peekbuf = p;
420  res = tls_recv(server[handle].eco,
421  server[handle].peekbuf, len, 0);
422  if(0 < res)
423  {
424  server[handle].peekbuf_len = (size_t) res;
425  res = -1; /* Result is set below after the data was copied */
426  }
427  }
428  }
429  /* Return data from peek buffer first */
430  if(server[handle].peekbuf_len)
431  {
432  if(server[handle].peekbuf_len < len)
433  {
434  /* Reduce request to size of buffered data */
435  len = server[handle].peekbuf_len;
436  }
437  memcpy(buf, server[handle].peekbuf, len);
438  res = (posix_ssize_t) len;
439  /* Remove data from peek buffer in read mode */
440  if(!peek)
441  {
442  p = (char*) server[handle].peekbuf;
443  memmove(server[handle].peekbuf, (void*) &p[len],
444  server[handle].peekbuf_len - len);
445  server[handle].peekbuf_len -= len;
446  }
447  }
448  }
449 # else /* CFG_USE_OPENSSL_API_1_1 && !CFG_USE_OPENSSL_API_3 */
450  /* OpenSSL 1.0.x and 3.x.x can handle the peek flag */
451  res = tls_recv(server[handle].eco, buf, len, peek);
452 # endif /* CFG_USE_OPENSSL_API_1_1 && !CFG_USE_OPENSSL_API_3 */
453  }
454  else
455 #endif /* CFG_USE_TLS */
456  {
457  res = posix_recv(server[handle].sd, buf, len, flags);
458  }
459 
460  return(res);
461 }
462 
463 
464 /* ========================================================================== */
465 /* Receive data from server */
466 
467 static posix_ssize_t srv_recv(int handle, void* buf, size_t len, int flags)
468 {
469 #if !CFG_CMPR_DISABLE
470  if(server[handle].compress_active)
471  {
472  return(cmpr_recv(server[handle].compress_stream, buf, len, flags));
473  }
474  else
475 #endif /* !CFG_CMPR_DISABLE */
476  {
477  return(transport_recv(handle, buf, len, flags));
478  }
479 }
480 
481 
482 /* ========================================================================== */
483 /* Create response */
484 
485 static int create_response(struct nntp_response** r)
486 {
487  int res = 0;
488 
489  *r = (struct nntp_response*) posix_malloc(sizeof(struct nntp_response));
490  if(NULL == *r)
491  {
492  PRINT_ERROR("Out of memory while allocating response");
493  res = -1;
494  }
495  else
496  {
497  /* Init optional fields */
498  (*r)->msg = NULL;
499  (*r)->content = NULL;
500  (*r)->lines = 0;
501  (*r)->bufsize = 0;
502  }
503 
504  return(res);
505 }
506 
507 
508 /* ========================================================================== */
509 /* Destroy response */
510 
511 static void destroy_response(struct nntp_response** r)
512 {
513  if(NULL != *r)
514  {
515  posix_free((void*) (*r)->msg);
516  posix_free((void*) (*r)->content);
517  posix_free((void*) *r);
518  }
519 }
520 
521 
522 /* ========================================================================== */
523 /* Check whether status code indicates a multiline response
524  *
525  * RFC 3977 specifies:
526  * Exceptions are the commands GROUP and LISTGROUP.
527  * In all other cases, the client MUST only use the status indicator itself to
528  * determine the nature of the response.
529  */
530 
531 static int multiline(unsigned int status, enum nntp_cmd command)
532 {
533  /* List with status codes that correspond to multiline responses */
534  static unsigned int code[12] =
535  {
536  100U, 101U, 211U, 215U, 220U, 221U, 222U, 224U, 225U, 230U, 231U,
537  0U /* A zero must terminate the list! */
538  };
539  int res = 0;
540  unsigned int i = 0;
541 
542  /* Check whether status code indicates a multiline response */
543  while(code[i]) { if(status == code[i++]) { res = 1; } }
544 
545  /* Exception for GROUP command (that use a 211 single line response) */
546  if(211U == status && NNTP_CMD_GROUP == command) { res = 0; }
547 
548  return(res);
549 }
550 
551 
552 /* ========================================================================== */
553 /* Receive data block of multiline response */
554 
555 static int recv_multiline_data_block(int handle,
556  struct nntp_response* response)
557 {
558  int res = 0;
559  posix_ssize_t rv;
560  char* resp = NULL;
561  char* p;
562  size_t len = 512;
563  size_t ri = 0;
564  size_t pi;
565  size_t i;
566  int eor = 0; /* End of record (data block) */
567  size_t lines = 0;
568 
569  /*
570  * Note:
571  * The line lengths inside the data block may be arbitrary.
572  * We allocate 512Byte at the beginning and expontially increase the size
573  * if required.
574  */
575  resp = (char*) posix_malloc(len);
576  if(NULL == resp)
577  {
578  PRINT_ERROR("Out of memory while receiving response");
579  res = -1;
580  }
581 
582  /* Receive data */
583  while(!res && !eor)
584  {
585  while(!res && !eor && len > ri)
586  {
587  /* Peek into next chunk of input */
588  pi = ri;
589  do { rv = srv_recv(handle, &resp[ri], len - ri, POSIX_MSG_PEEK); }
590  while((posix_ssize_t) -1 == rv && POSIX_EINTR == posix_errno);
591  if((posix_ssize_t) -1 == rv)
592  {
593  PRINT_ERROR("Reading data failed");
594  res = -2;
595  break;
596  }
597  pi += (size_t) rv;
598  /* 'pi' now points behind the last character */
599  /* Check for EOR */
600  i = ri;
601  while(pi > i)
602  {
603  /* Check for EOL */
604  if('\n' == resp[i])
605  {
606  ++lines;
607  if(2 == i)
608  {
609  if('\r' == resp[i - 1] && '.' == resp[i - 2])
610  {
611  /* Empty data block */
612  eor = 1;
613  break;
614  }
615  }
616  else if(4 <= i)
617  {
618  if('\r' == resp[i - 1] && '.' == resp[i - 2]
619  && '\n' == resp[i - 3] && '\r' == resp[i - 4])
620  {
621  eor = 1;
622  break;
623  }
624  }
625  }
626  ++i;
627  }
628  if(eor)
629  {
630  ++i;
631  --lines;
632  }
633  /* 'i' now points behind the last character */
634  /* Read next chunk of input (up to but not including index 'i') */
635  while(i > ri)
636  {
637  do { rv = srv_recv(handle, &resp[ri], i - ri, 0); }
638  while((posix_ssize_t) -1 == rv && POSIX_EINTR == posix_errno);
639  if((posix_ssize_t) -1 == rv)
640  {
641  PRINT_ERROR("Reading data failed");
642  res = -2;
643  break;
644  }
645  ri += (size_t) rv;
646  }
647  /* 'ri' now points behind the last character */
648  }
649  /* Allocate more memory if required */
650  if(!res && !eor)
651  {
652  len *= 2;
653  p = (char*) posix_realloc(resp, len);
654  if(NULL == p)
655  {
656  PRINT_ERROR("Out of memory while receiving response");
657  res = -1;
658  }
659  else { resp = p; }
660  }
661  }
662  if(!res && !eor) { res = -1; }
663  if(res) { posix_free((void*) resp); }
664  else
665  {
666  /* Replace ".\r\n" termination with zero termination */
667  len = ri - 3;
668  resp[len] = 0;
669  /* Remove dot stuffing */
670  if(len)
671  {
672  if('.' == resp[0] && '.' == resp[1])
673  {
674  memmove(&resp[0], &resp[1], --len + (size_t) 1);
675  }
676  for(i = 2; i < len; ++i)
677  {
678  if('.' == resp[i] && '.' == resp[i - 1] && (char) 10 == resp[i - 2])
679  {
680  memmove(&resp[i - 1], &resp[i], len-- - i + (size_t) 1);
681  }
682  }
683  }
684  if(NULL != response->content) { posix_free((void*) response->content); }
685  response->content = resp;
686  response->lines = lines;
687  response->bufsize = len + 1; /* For the terminating NUL */
688  log_add(server[handle].lfs, response->content);
689  }
690 
691  return(res);
692 }
693 
694 
695 /* ========================================================================== */
696 /* Receive response */
697 
698 static int recv_reply(int handle, enum nntp_cmd command,
699  struct nntp_response* response)
700 {
701  int res = 0;
702  posix_ssize_t rv;
703  char resp[NNTP_LINELENGTHMAX + (size_t) 1];
704  size_t len = NNTP_LINELENGTHMAX;
705  size_t li = 0;
706  size_t pi;
707  size_t i;
708  int eol = 0;
709 
710  /* Receive first line of response */
711  while(!res && !eol && len > li)
712  {
713  /* Peek into next chunk of input */
714  pi = li;
715  do { rv = srv_recv(handle, &resp[li], len - li, POSIX_MSG_PEEK); }
716  while((posix_ssize_t) -1 == rv && POSIX_EINTR == posix_errno);
717  /* Treat zero (connection was closed by peer) as error */
718  if((posix_ssize_t) 0 >= rv)
719  {
720  PRINT_ERROR("Reading data failed");
721  res = -2;
722  break;
723  }
724  pi += (size_t) rv;
725  /* Check for EOL */
726  i = li;
727  while(pi > i)
728  {
729  if('\n' == resp[i++])
730  {
731  eol = 1;
732  break;
733  }
734  }
735  /* Read next chunk of input (up to but not including index 'i') */
736  while(i > li)
737  {
738  do { rv = srv_recv(handle, &resp[li], i - li, 0); }
739  while((posix_ssize_t) -1 == rv && POSIX_EINTR == posix_errno);
740  if((posix_ssize_t) -1 == rv)
741  {
742  PRINT_ERROR("Reading data failed");
743  res = -2;
744  break;
745  }
746  li += (size_t) rv;
747  }
748  }
749  if(!res && !eol) { res = -1; }
750  if(!res)
751  {
752  /* Store first line of response */
753  resp[li] = 0;
754  resp[NNTP_LINELENGTHMAX] = 0;
755  for(i = 0; i < 3; ++i)
756  {
757  if((unsigned char) 9 >= (unsigned char) resp[i])
758  {
759  PRINT_ERROR("Protocol error: Invalid response code");
760  log_add(server[handle].lfs, "[<= Invalid response]\n");
761  res = -1;
762  }
763  }
764  if(!res)
765  {
766  response->code1 = (unsigned char) (resp[0] - (char) 48);
767  response->code2 = (unsigned char) (resp[1] - (char) 48);
768  response->code3 = (unsigned char) (resp[2] - (char) 48);
769  if(NULL != response->msg) { posix_free((void*) response->msg); }
770  response->msg = (char*) posix_malloc(strlen(resp) + (size_t) 1);
771  if(NULL == response->msg)
772  {
773  PRINT_ERROR("Out of memory while receiving response");
774  res = -1;
775  }
776  else { strcpy(response->msg, resp); }
777  response->content = NULL;
778  response->lines = 0;
779  log_add(server[handle].lfs, "[<=] ");
780  log_add(server[handle].lfs, response->msg);
781  }
782  }
783 
784  /* Check for success */
785  if(!res)
786  {
787  response->status = (unsigned int) response->code3;
788  response->status += (unsigned int) response->code2 * 10U;
789  response->status += (unsigned int) response->code1 * 100U;
790  /* Check whether the response is multiline */
791  if(multiline(response->status, command))
792  {
793  log_add(server[handle].lfs, "[<= Expect multiline data block]\n");
794  res = recv_multiline_data_block(handle, response);
795  }
796  }
797 
798  return(res);
799 }
800 
801 
802 /* ========================================================================== */
803 /* Send multiline data block */
804 
805 static int send_multiline_data_block(int handle, const char* data)
806 {
807  static const char eob[] = ".\r\n";
808  int res = 0;
809  size_t len;
810  size_t i;
811  posix_ssize_t rv;
812  char* buf = NULL;
813  size_t bi;
814  char c;
815 
816  if(NULL == data) { res = -1; }
817  else
818  {
819  log_add(server[handle].lfs, "[=> Send multiline data block]\n");
820  /*
821  * Verify data and add dot stuffing
822  * Calculate 3 additional bytes for potentially missing EOL mark and NUL.
823  * Allocating a buffer twice as large is always sufficient.
824  * Eliminate single CR or LF characters (not parts of EOL mark).
825  * Ensure that (non empty) data block ends with CRLF.
826  */
827  buf = (char*) posix_malloc((strlen(data) + (size_t) 3) * (size_t) 2);
828  if(NULL == buf)
829  {
830  PRINT_ERROR("Memory allocation failed for dot stuffing");
831  data = "";
832  }
833  else
834  {
835  i = 0; bi = 0;
836  /* Assignment in truth expression is intended */
837  while((c = data[i++]))
838  {
839  if( (13 == (int) c && 10 != (int) data[i])
840  || (10 == (int) c && 13 != (int) data[i - (size_t) 2] && i) )
841  {
842  continue;
843  }
844  if('.' == c && (size_t) 3 <= i)
845  {
846  if( (10 == (int) data[i - (size_t) 2])
847  && (13 == (int) data[i - (size_t) 3]) )
848  {
849  /* Add additional dot */
850  buf[bi++] = '.';
851  }
852  }
853  buf[bi++] = c;
854  }
855  /* Check for CR+LF at end of (non empty) buffer */
856  if(bi)
857  {
858  if( (size_t) 2 > bi
859  || 10 != (int) buf[bi - (size_t) 1]
860  || 13 != (int) buf[bi - (size_t) 2] )
861  {
862  /* Add missing EOL mark */
863  buf[bi++] = (char) 13;
864  buf[bi++] = (char) 10;
865  }
866  }
867  /* Terminate new data */
868  buf[bi++] = 0;
869  data = buf;
870  }
871  /* Send data */
872  log_add(server[handle].lfs, data);
873  len = strlen(data);
874  i = 0;
875  while(i < len)
876  {
877  do { rv = srv_send(handle, &data[i], len - i); }
878  while((posix_ssize_t) -1 == rv && POSIX_EINTR == posix_errno);
879  if((posix_ssize_t) -1 == rv)
880  {
881  PRINT_ERROR("Writing data failed");
882  res = -2;
883  break;
884  }
885  i += (size_t) rv;
886  }
887  posix_free((void*) buf);
888  /* Send EOB mark */
889  log_add(server[handle].lfs, eob);
890  len = strlen(eob);
891  i = 0;
892  while(i < len)
893  {
894  do { rv = srv_send(handle, &eob[i], len - i); }
895  while((posix_ssize_t)-1 == rv && POSIX_EINTR == posix_errno);
896  if((posix_ssize_t) -1 == rv)
897  {
898  PRINT_ERROR("Writing data failed");
899  res = -2;
900  break;
901  }
902  i += (size_t) rv;
903  }
904  /* Flush TX direction of transport layer */
905  if(!res)
906  {
907  res = transport_flush(handle, 0);
908  if(0 > res) { res = -2; }
909  }
910  }
911 
912  return(res);
913 }
914 
915 
916 /* ========================================================================== */
917 /* Execute command on NNTP server
918  *
919  * This function must be reentrant.
920  *
921  * \return
922  * - 0 on success
923  * - -1 on error
924  * - -2 if the connection to the server is broken
925  * - -3 if authentication requested by server failed
926  */
927 
928 static int exec_cmd(int handle, enum nntp_cmd command,
929  struct nntp_response* response, ...)
930 {
931  va_list ap; /* Object for argument list handling */
932  int res = 0;
933  char cmd[NNTP_LINELENGTHMAX + (size_t) 1];
934  posix_ssize_t rv;
935  size_t len;
936  size_t i;
937  const char* s;
938  nntp_anum_t article_id;
939  char article_asc[17];
940  int auth = 0; /* Commands used for authentication must set this flag */
941  int retry = 1; /* Commands that should never retried can clear this flag */
942  int post = 0; /* The POST command must set this flag */
943  const char* data = NULL;
944 
945  /* Be prepared for retry after potential authentication */
946  do
947  {
948  if(0 > retry)
949  {
950  PRINT_ERROR("Bug in command retry state machine");
951  res = -1;
952  break;
953  }
954 
955  /* Prepare command */
956  va_start(ap, response);
957  switch(command)
958  {
959  case NNTP_CMD_QUIT:
960  {
961  retry = 0;
962  strcpy(cmd, "QUIT\r\n");
963  break;
964  }
965  case NNTP_CMD_CAPABILITIES:
966  {
967  retry = 0;
968  strcpy(cmd, "CAPABILITIES\r\n");
969  break;
970  }
971  case NNTP_CMD_MODE_READER:
972  {
973  retry = 0;
974  strcpy(cmd, "MODE READER\r\n");
975  break;
976  }
977  case NNTP_CMD_MAXARTNUM:
978  {
979  strcpy(cmd, "MAXARTNUM ");
980  s = va_arg(ap, const char*);
981  len = NNTP_LINELENGTHMAX - strlen(cmd) - (size_t) 2;
982  strncat(cmd, s, len);
983  strncat(cmd, "\r\n", 2);
984  break;
985  }
986  case NNTP_CMD_COMPRESS:
987  {
988  strcpy(cmd, "COMPRESS ");
989  s = va_arg(ap, const char*);
990  len = NNTP_LINELENGTHMAX - strlen(cmd) - (size_t) 2;
991  strncat(cmd, s, len);
992  strncat(cmd, "\r\n", 2);
993  break;
994  }
995  case NNTP_CMD_LIST:
996  {
997  /* Check whether server has LIST capability */
998  if(!(server[handle].capabilities & NNTP_CAPA_LIST))
999  {
1000  PRINT_ERROR("Server has no LIST capability");
1001  res = -1;
1002  }
1003  else
1004  {
1005  /* Yes, use ACTIVE argument if supported */
1006  if(1U < server[handle].version)
1007  {
1008  strcpy(cmd, "LIST ACTIVE\r\n");
1009  }
1010  else { strcpy(cmd, "LIST\r\n"); }
1011  }
1012  break;
1013  }
1014  case NNTP_CMD_LIST_NEWSGROUPS:
1015  {
1016  /* Check whether server has LIST capability */
1017  if(!(server[handle].capabilities & NNTP_CAPA_LIST))
1018  {
1019  PRINT_ERROR("Server has no LIST capability");
1020  res = -1;
1021  }
1022  else { strcpy(cmd, "LIST NEWSGROUPS\r\n"); }
1023  break;
1024  }
1025  case NNTP_CMD_LIST_MOTD:
1026  {
1027  /* Check whether server has LIST MOTD capability */
1028  if(!(server[handle].capabilities & NNTP_CAPA_LIST_MOTD))
1029  {
1030  PRINT_ERROR("Server has no LIST MOTD capability");
1031  res = -1;
1032  }
1033  else { strcpy(cmd, "LIST MOTD\r\n"); }
1034  break;
1035  }
1036  case NNTP_CMD_LIST_DISTRIB_PATS:
1037  {
1038  /* Check whether server has LIST DISTRIB.PATS capability */
1039  if(!(server[handle].capabilities & NNTP_CAPA_LIST_DISTRIB_PATS))
1040  {
1041  PRINT_ERROR("Server has no LIST DISTRIB.PATS capability");
1042  res = -1;
1043  }
1044  else { strcpy(cmd, "LIST DISTRIB.PATS\r\n"); }
1045  break;
1046  }
1047  case NNTP_CMD_LIST_SUBSCRIPTIONS:
1048  {
1049 #if 0 /* Disable NNTP V2 capability check, should work with NNTP V1 too */
1050  /* Check whether server has LIST SUBSCRIPTIONS capability */
1051  if(!(server[handle].capabilities & NNTP_CAPA_LIST_SUBSCRIPTIONS))
1052  {
1053  PRINT_ERROR("Server has no LIST SUBSCRIPTIONS capability");
1054  res = -1;
1055  }
1056  else
1057 #endif
1058  { strcpy(cmd, "LIST SUBSCRIPTIONS\r\n"); }
1059  break;
1060  }
1061  case NNTP_CMD_LIST_OVERVIEW_FMT:
1062  {
1063  /* Check whether server has OVER capability */
1064  if(!(server[handle].capabilities & NNTP_CAPA_OVER))
1065  {
1066  PRINT_ERROR("Server has no OVER capability");
1067  res = -1;
1068  }
1069  else { strcpy(cmd, "LIST OVERVIEW.FMT\r\n"); }
1070  break;
1071  }
1072  case NNTP_CMD_GROUP:
1073  {
1074  strcpy(cmd, "GROUP ");
1075  s = va_arg(ap, const char*);
1076  len = strlen(s);
1077  if(NNTP_LINELENGTHMAX - strlen(cmd) - (size_t) 2 < len)
1078  {
1079  PRINT_ERROR("Value of NNTP_LINELENGTHMAX too small");
1080  res = -1;
1081  break;
1082  }
1083  strncat(cmd, s, len);
1084  strncat(cmd, "\r\n", 2);
1085  break;
1086  }
1087  case NNTP_CMD_OVER:
1088  {
1089  /* Check whether server has OVER capability */
1090  if(!(server[handle].capabilities & NNTP_CAPA_OVER))
1091  {
1092  PRINT_ERROR("Server has no OVER capability");
1093  res = -1;
1094  }
1095  else
1096  {
1097  strcpy(cmd, "OVER ");
1098  article_id = *va_arg(ap, nntp_anum_t*);
1099  if(0 > enc_convert_anum_to_ascii(article_asc, &len,
1100  (core_anum_t) article_id))
1101  {
1102  res = -1;
1103  break;
1104  }
1105  if(NNTP_LINELENGTHMAX - strlen(cmd) - (size_t) 2
1106  < len + (size_t) 1)
1107  {
1108  PRINT_ERROR("Value of NNTP_LINELENGTHMAX too small");
1109  res = -1;
1110  break;
1111  }
1112  strncat(cmd, article_asc, len);
1113  strncat(cmd, "-", 1);
1114  article_id = *va_arg(ap, nntp_anum_t*);
1115  if(0 > enc_convert_anum_to_ascii(article_asc, &len,
1116  (core_anum_t) article_id))
1117  {
1118  res = -1;
1119  break;
1120  }
1121  if(NNTP_LINELENGTHMAX - strlen(cmd) - (size_t) 2 < len)
1122  {
1123  PRINT_ERROR("Value of NNTP_LINELENGTHMAX too small");
1124  res = -1;
1125  break;
1126  }
1127  strncat(cmd, article_asc, len);
1128  strncat(cmd, "\r\n", 2);
1129  }
1130  break;
1131  }
1132  case NNTP_CMD_ARTICLE_BY_MID:
1133  {
1134  strcpy(cmd, "ARTICLE ");
1135  s = va_arg(ap, const char*);
1136  len = strlen(s);
1137  if(NNTP_LINELENGTHMAX - strlen(cmd) - (size_t) 2 < len)
1138  {
1139  PRINT_ERROR("Value of NNTP_LINELENGTHMAX too small");
1140  res = -1;
1141  break;
1142  }
1143  strncat(cmd, s, len);
1144  strncat(cmd, "\r\n", 2);
1145  break;
1146  }
1147  case NNTP_CMD_ARTICLE:
1148  case NNTP_CMD_HEAD:
1149  case NNTP_CMD_BODY:
1150  {
1151  article_id = *va_arg(ap, nntp_anum_t*);
1152  if(0 > enc_convert_anum_to_ascii(article_asc, &len,
1153  (core_anum_t) article_id))
1154  {
1155  res = -1;
1156  break;
1157  }
1158  if(NNTP_CMD_HEAD == command) { strcpy(cmd, "HEAD "); }
1159  else if(NNTP_CMD_BODY == command) { strcpy(cmd, "BODY "); }
1160  else { strcpy(cmd, "ARTICLE "); }
1161  if(NNTP_LINELENGTHMAX - strlen(cmd) - (size_t) 2 < len)
1162  {
1163  PRINT_ERROR("Value of NNTP_LINELENGTHMAX too small");
1164  res = -1;
1165  break;
1166  }
1167  strncat(cmd, article_asc, len);
1168  strncat(cmd, "\r\n", 2);
1169  break;
1170  }
1171  case NNTP_CMD_POST:
1172  {
1173  /* Check whether server has POST capability */
1174  if(!(server[handle].capabilities & NNTP_CAPA_POST))
1175  {
1176  PRINT_ERROR("Server has no POST capability");
1177  res = -1;
1178  }
1179  else
1180  {
1181  strcpy(cmd, "POST\r\n");
1182  data = va_arg(ap, const char*);
1183  post = 1;
1184  }
1185  break;
1186  }
1187 #if CFG_USE_TLS
1188  case NNTP_CMD_AUTHINFO_USER:
1189  {
1190  auth = 1;
1191  strcpy(cmd, "AUTHINFO USER ");
1192  len = strlen(server[handle].user);
1193  if(NNTP_LINELENGTHMAX - strlen(cmd) - (size_t) 2 < len)
1194  {
1195  PRINT_ERROR("Value of NNTP_LINELENGTHMAX too small");
1196  res = -1;
1197  break;
1198  }
1199  strncat(cmd, server[handle].user, len);
1200  strncat(cmd, "\r\n", 2);
1201  break;
1202  }
1203  case NNTP_CMD_AUTHINFO_PASS:
1204  {
1205  auth = 1;
1206  strcpy(cmd, "AUTHINFO PASS ");
1207  len = strlen(server[handle].passwd);
1208  if(NNTP_LINELENGTHMAX - strlen(cmd) - (size_t) 2 < len)
1209  {
1210  PRINT_ERROR("Value of NNTP_LINELENGTHMAX too small");
1211  res = -1;
1212  break;
1213  }
1214  strncat(cmd, server[handle].passwd, len);
1215  strncat(cmd, "\r\n", 2);
1216  break;
1217  }
1218 #endif /* CFG_USE_TLS */
1219  default:
1220  {
1221  PRINT_ERROR("Request to execute unknown command");
1222  res = -1;
1223  break;
1224  }
1225  }
1226 
1227  /* Send command */
1228  if(!res)
1229  {
1230  /* Avoid writing password to logfile */
1231  if(NNTP_CMD_AUTHINFO_PASS == command)
1232  {
1233  log_add(server[handle].lfs, "[=>] AUTHINFO PASS ********\n");
1234  /* printf("[Command] AUTHINFO PASS ********\n"); */
1235  }
1236  else
1237  {
1238  log_add(server[handle].lfs, "[=>] ");
1239  log_add(server[handle].lfs, cmd);
1240  /* printf("[Command] %s", cmd); */
1241  }
1242  len = strlen(cmd);
1243  i = 0;
1244  while(i < len)
1245  {
1246  do { rv = srv_send(handle, &cmd[i], len - i); }
1247  while((posix_ssize_t) -1 == rv && POSIX_EINTR == posix_errno);
1248  if((posix_ssize_t) -1 == rv)
1249  {
1250  PRINT_ERROR("Writing data failed");
1251  res = -2;
1252  break;
1253  }
1254  i += (size_t) rv;
1255  }
1256  /* Flush TX direction of transport layer */
1257  if(!res)
1258  {
1259  if(NNTP_CMD_QUIT == command)
1260  {
1261  /* Indicate termination to transport layer */
1262  res = transport_flush(handle, 1);
1263  }
1264  else
1265  {
1266  res = transport_flush(handle, 0);
1267  }
1268  if(0 > res)
1269  {
1270  PRINT_ERROR("Failed to flush transport layer");
1271  res = -2;
1272  }
1273  }
1274  }
1275  va_end(ap);
1276 
1277  /* Receive reply */
1278  if(!res) { res = recv_reply(handle, command, response); }
1279 
1280  /*
1281  * Check whether authentication is required here while the last command
1282  * and its parameters are still available on the stack for retry.
1283  */
1284  if(res || auth) { break; }
1285  else if(480U != response->status) { retry = 0; }
1286  else
1287  {
1288  /* Server requests authentication */
1289  if(exec_auth(handle))
1290  {
1291  /* Authentication failed */
1292  log_add(server[handle].lfs, "[Authentication failed]\n");
1293  res = -3;
1294  }
1295  else { res = refresh_capabilities(handle); }
1296  }
1297  }
1298  while(!res && retry--);
1299 
1300  /* Check whether server have requested article to post */
1301  if(!res && post && 3U == response->code1 && 4U == response->code2)
1302  {
1303  /* Yes => Send article as multiline data block */
1304  res = send_multiline_data_block(handle, data);
1305 
1306  /* Receive reply */
1307  if(!res) { res = recv_reply(handle, command, response); }
1308  }
1309 
1310  return(res);
1311 }
1312 
1313 
1314 /* ========================================================================== */
1315 /* Execute authentication on NNTP server */
1316 
1317 static int exec_auth(int handle)
1318 {
1319  int res = -1;
1320  int rv;
1321  struct nntp_response* response = NULL;
1322 
1323  /* Allocate memory for response */
1324  rv = create_response(&response);
1325  if(rv) { PRINT_ERROR("Cannot allocate memory for response"); }
1326  else
1327  {
1328  /* Authenticate against server */
1329  switch(server[handle].auth)
1330  {
1331  case -1:
1332  {
1333  PRINT_ERROR("Error: Authentication requested more than once");
1334  break;
1335  }
1336  case 0:
1337  {
1338  PRINT_ERROR("Authentication required but disabled");
1339  break;
1340  }
1341  case 1:
1342  {
1343  /* Check whether server has AUTHINFO USER capability */
1344  if(!(server[handle].capabilities & NNTP_CAPA_AUTHINFO_USER))
1345  {
1346  PRINT_ERROR("Server has no AUTHINFO USER capability");
1347  }
1348  else
1349  {
1350 #if CFG_USE_TLS
1351  /* RFC 2980 / RFC 4643 conformant AUTHINFO USER authentication */
1352  rv = exec_cmd(handle, NNTP_CMD_AUTHINFO_USER, response);
1353  if(!rv && 381U == response->status)
1354  {
1355  rv = exec_cmd(handle, NNTP_CMD_AUTHINFO_PASS, response);
1356  if(!rv && 281U == response->status)
1357  {
1358  /* Don't accept further authentication requests */
1359  server[handle].auth = -1;
1360  res = 0;
1361  }
1362  }
1363  if(res)
1364  {
1365  PRINT_ERROR("Authentication was not accepted");
1366  }
1367 #else /* CFG_USE_TLS */
1368  PRINT_ERROR("No TLS support (required for authentication)");
1369 #endif /* CFG_USE_TLS */
1370  }
1371  break;
1372  }
1373  default:
1374  {
1375  PRINT_ERROR("Authentication algorithm not supported");
1376  break;
1377  }
1378  }
1379  }
1380  /* Release memory for response */
1381  destroy_response(&response);
1382 
1383  return(res);
1384 }
1385 
1386 
1387 /* ========================================================================== */
1388 /* Enable compression on NNTP server
1389  *
1390  * \return
1391  * - 0 on success
1392  * - 1 if negotiation of the mandatory DEFLATE algorithm failed
1393  * - -1 on error
1394  */
1395 
1396 #if !CFG_CMPR_DISABLE
1397 static int exec_compress(int handle)
1398 {
1399  int res = -1;
1400  int rv;
1401  struct nntp_response* response = NULL;
1402  const char* alg = NULL;
1403 
1404  /* Allocate memory for response */
1405  rv = create_response(&response);
1406  if(rv) { PRINT_ERROR("Cannot allocate memory for response"); }
1407  else
1408  {
1409  /* Check whether server has COMPRESS capability */
1410  if(!(server[handle].capabilities & NNTP_CAPA_COMPRESS))
1411  {
1412  PRINT_ERROR("Server has no COMPRESS capability (bug)");
1413  }
1414  else
1415  {
1416  /* Select compression algorithm */
1417  if(server[handle].compress_algs & NNTP_COMPRESS_DEFLATE)
1418  {
1419 # if CFG_USE_ZLIB
1420  /* DEFLATE according to RFC 1951 */
1421  alg = "DEFLATE";
1422 # endif /* CFG_USE_ZLIB */
1423  }
1424  if(NULL == alg)
1425  {
1426  PRINT_ERROR("No matching compression algorithm available");
1427  res = 1;
1428  }
1429  else
1430  {
1431  /* Enable compression */
1432  rv = exec_cmd(handle, NNTP_CMD_COMPRESS, response, alg);
1433  if(!rv && 206U == response->status)
1434  {
1435  /* NNTP negotiation for COMPRESS extension successful */
1436  log_add(server[handle].lfs,
1437  "[Using COMPRESS (RFC 8054) extension]\n");
1438  res = 0;
1439  }
1440  }
1441  }
1442  }
1443  /* Release memory for response */
1444  destroy_response(&response);
1445 
1446  /* Create compressed data stream object */
1447  if(!res)
1448  {
1449  server[handle].compress_stream = cmpr_stream_constructor(CMPR_ALG_DEFLATE,
1450  handle,
1451  transport_send,
1452  transport_recv);
1453  if(NULL == server[handle].compress_stream) { res = -1; }
1454  else { server[handle].compress_active = 1; }
1455  }
1456 
1457  /* Check for error */
1458  if(res) { PRINT_ERROR("Enabling compression failed"); }
1459 
1460  return(res);
1461 }
1462 #endif /* !CFG_CMPR_DISABLE */
1463 
1464 
1465 /* ========================================================================== */
1466 /* Get current capabilities from server */
1467 
1468 static int refresh_capabilities(int handle)
1469 {
1470  int res = -1;
1471  struct nntp_response* response = NULL;
1472  int rv;
1473  unsigned int v[2];
1474  unsigned int ver;
1475  size_t i, j;
1476  size_t len;
1477  char* p;
1478  unsigned int version_old = server[handle].version;
1479 
1480  /* Allocate memory for response */
1481  res = create_response(&response);
1482  if(!res)
1483  {
1484  /*
1485  * Since NNTP version 2 the CAPABILITIES command is mandatory
1486  * If the current value is 1, this means that the CAPABILITIES command is
1487  * not supported and we do not send it again.
1488  */
1489  if(1U == version_old) { res = 0; }
1490  else
1491  {
1492  res = exec_cmd(handle, NNTP_CMD_CAPABILITIES, response);
1493  if(!res)
1494  {
1495  /* Parse capabilities */
1496  if(101U != response->status)
1497  {
1498  /* NNTP V1 */
1499  log_add(server[handle].lfs,
1500  "[Switch to NNTP V1 (RFC 977) mode]\n");
1501  server[handle].version = 1;
1502  /* Expect all required capabilities to be present */
1503  server[handle].capabilities = NNTP_CAPA_READER | NNTP_CAPA_LIST
1504  | NNTP_CAPA_POST
1505  | NNTP_CAPA_AUTHINFO_USER;
1506  }
1507  else
1508  {
1509  /* Capabilities must be parsed case insensitive */
1510  len = strlen(response->content);
1511  for(i = 0; i < len; ++i)
1512  {
1513  response->content[i] = (char)
1514  toupper((int) response->content[i]);
1515  }
1516  /*
1517  * Extract protocol version from first line
1518  * Be prepared for something like "2 3".
1519  */
1520  rv = sscanf(response->content, "VERSION %u %u", &v[0], &v[1]);
1521  if(1 > rv)
1522  {
1523  PRINT_ERROR("Protocol error: No version information");
1524  }
1525  else
1526  {
1527  /* Select highest supported protocol version */
1528  ver = 1;
1529  for(i = 0; i < (size_t) rv; ++i)
1530  {
1531  if(v[i] > ver) { ver = v[i]; }
1532  if(2U == ver) { break; }
1533  }
1534  if(2U == ver)
1535  {
1536  if(2U != version_old)
1537  {
1538  log_add(server[handle].lfs,
1539  "[Switch to NNTP V2 (RFC 3977) mode]\n");
1540  }
1541  server[handle].version = 2U;
1542  }
1543  else
1544  {
1545  if(1U != ver)
1546  {
1547  log_add(server[handle].lfs,
1548  "[Advertised protocol not supported]\n");
1549  }
1550  if(1U != version_old)
1551  {
1552  log_add(server[handle].lfs,
1553  "[Switch to NNTP V1 (RFC 977) mode]\n");
1554  }
1555  server[handle].version = 1U;
1556  /* Expect all required capabilities to be present */
1557  server[handle].capabilities = NNTP_CAPA_READER
1558  | NNTP_CAPA_LIST
1559  | NNTP_CAPA_POST
1560  | NNTP_CAPA_AUTHINFO_USER;
1561  }
1562 
1563  /* Parse capabilities */
1564  if(2U <= ver)
1565  {
1566  server[handle].capabilities = 0;
1567  if(NULL != strstr(response->content, "\nMODE-READER"))
1568  {
1569  server[handle].capabilities |= NNTP_CAPA_MODE_READER;
1570  }
1571  if(NULL != strstr(response->content, "\nREADER"))
1572  {
1573  server[handle].capabilities |= NNTP_CAPA_READER;
1574  }
1575  p = strstr(response->content, "\nMAXARTNUM");
1576  if(NULL != p)
1577  {
1578  i = 0; while(p[++i])
1579  {
1580  if(!enc_ascii_check_digit(&p[i]))
1581  {
1582  for (j = i, len = 0; i + (size_t) 20 > j; ++j)
1583  {
1584  if(!enc_ascii_check_digit(&p[j])) { ++len; }
1585  }
1587  &server[handle].maxartnum, &p[i], len);
1588  if (-2 == rv)
1589  {
1590  /* Too large => Clamp to our limit */
1591  server[handle].maxartnum = NNTP_ANUM_T_MAX;
1592  rv = 0;
1593  }
1594  else if (!rv
1595  && (nntp_anum_t) 2147483647UL
1596  >= server[handle].maxartnum)
1597  {
1598  /* Too small => Do not accept */
1599  server[handle].maxartnum = 0;
1600  rv = -1;
1601  }
1602  if(!rv)
1603  {
1604  server[handle].capabilities
1605  |= NNTP_CAPA_MAXARTNUM;
1606  }
1607  break;
1608  }
1609  }
1610  }
1611  p = strstr(response->content, "\nLIST");
1612  if(NULL != p)
1613  {
1614  server[handle].capabilities |= NNTP_CAPA_LIST;
1615  i = 0; while(p[++i])
1616  {
1617  if('\n' == p[i]) { p[i] = 0; break; }
1618  }
1619  if(NULL != strstr(p, "MOTD"))
1620  {
1621  server[handle].capabilities |= NNTP_CAPA_LIST_MOTD;
1622  }
1623  if(NULL != strstr(p, "DISTRIB.PATS"))
1624  {
1625  if(config[CONF_DIST_SUGG].val.i)
1626  {
1627  server[handle].capabilities
1628  |= NNTP_CAPA_LIST_DISTRIB_PATS;
1629  }
1630  }
1631  if(NULL != strstr(p, "SUBSCRIPTIONS"))
1632  {
1633  server[handle].capabilities
1634  |= NNTP_CAPA_LIST_SUBSCRIPTIONS;
1635  }
1636  p[i] = '\n'; /* Remove temp. NUL termination again */
1637  }
1638  if(NULL != strstr(response->content, "\nOVER"))
1639  {
1640  if(!config[CONF_NO_OVER].val.i)
1641  {
1642  server[handle].capabilities |= NNTP_CAPA_OVER;
1643  }
1644  }
1645  if(NULL != strstr(response->content, "\nPOST"))
1646  {
1647  server[handle].capabilities |= NNTP_CAPA_POST;
1648  }
1649  p = strstr(response->content, "\nAUTHINFO");
1650  if(NULL != p)
1651  {
1652  i = 0; while(p[++i])
1653  {
1654  if('\n' == p[i]) { p[i] = 0; break; }
1655  }
1656  if(NULL != strstr(p, "USER"))
1657  {
1658  server[handle].capabilities
1659  |= NNTP_CAPA_AUTHINFO_USER;
1660  }
1661  p[i] = '\n'; /* Remove temp. NUL termination again */
1662  }
1663  p = strstr(response->content, "\nCOMPRESS");
1664  if(NULL != p)
1665  {
1666  i = 0; while(p[++i])
1667  {
1668  if('\n' == p[i]) { p[i] = 0; break; }
1669  }
1670  if(NULL != strstr(p, "DEFLATE"))
1671  {
1672  server[handle].capabilities |= NNTP_CAPA_COMPRESS;
1673  server[handle].compress_algs
1674  |= NNTP_COMPRESS_DEFLATE;
1675  }
1676  p[i] = '\n'; /* Remove temp. NUL termination again */
1677  if(!(server[handle].capabilities & NNTP_CAPA_COMPRESS))
1678  {
1679  PRINT_ERROR("Advertised compression "
1680  "algorithms not supported");
1681  }
1682  }
1683  }
1684  }
1685  }
1686  }
1687  }
1688  }
1689 
1690  /* Release memory for response */
1691  destroy_response(&response);
1692 
1693  return(res);
1694 }
1695 
1696 
1697 /* ========================================================================== */
1698 /* Get current overview format from server */
1699 
1700 static int refresh_overview_format(int handle)
1701 {
1702  int res = -1;
1703  struct nntp_response* response = NULL;
1704  size_t i;
1705  size_t len;
1706  size_t field; /* Index in the sense of OVER (0 is the article number) */
1707  char* bol; /* Beginning of line */
1708  const char* string;
1709  int error = 0;
1710  size_t newsgroups_available = 0;
1711 
1712  /* Return success if not available */
1713  if(!(NNTP_CAPA_OVER & server[handle].capabilities)) { res = 0; }
1714  else
1715  {
1716  /* Allocate memory for response */
1717  res = create_response(&response);
1718  if(!res)
1719  {
1720  res = exec_cmd(handle, NNTP_CMD_LIST_OVERVIEW_FMT, response);
1721  if(!res)
1722  {
1723  if(215U != response->status)
1724  {
1725  log_add(server[handle].lfs,
1726  "[Reading overview format failed]\n");
1727  res = -1;
1728  }
1729  else
1730  {
1731  /* Overview format must be parsed case insensitive */
1732  len = strlen(response->content);
1733  for(i = 0; i < len; ++i)
1734  {
1735  response->content[i] = (char)
1736  toupper((int) response->content[i]);
1737  }
1738  /* Check mandatory fields of overview format and their order */
1739  bol = response->content;
1740  field = 1;
1741  while (1)
1742  {
1743  switch(field)
1744  {
1745  case 0:
1746  {
1747  error = 1;
1748  break;
1749  }
1750  case 1:
1751  {
1752  string = "SUBJECT:";
1753  if(strncmp(bol, string, strlen(string))) { error = 1; }
1754  break;
1755  }
1756  case 2:
1757  {
1758  string = "FROM:";
1759  if(strncmp(bol, string, strlen(string))) { error = 1; }
1760  break;
1761  }
1762  case 3:
1763  {
1764  string = "DATE:";
1765  if(strncmp(bol, string, strlen(string))) { error = 1; }
1766  break;
1767  }
1768  case 4:
1769  {
1770  string = "MESSAGE-ID:";
1771  if(strncmp(bol, string, strlen(string))) { error = 1; }
1772  break;
1773  }
1774  case 5:
1775  {
1776  string = "REFERENCES:";
1777  if(strncmp(bol, string, strlen(string))) { error = 1; }
1778  break;
1779  }
1780  case 6:
1781  {
1782  string = ":BYTES";
1783  if(strncmp(bol, string, strlen(string)))
1784  {
1785  string = "BYTES:";
1786  if(strncmp(bol, string, strlen(string)))
1787  {
1788  error = 1;
1789  }
1790  }
1791  break;
1792  }
1793  case 7:
1794  {
1795  string = ":LINES";
1796  if(strncmp(bol, string, strlen(string)))
1797  {
1798  string = "LINES:";
1799  if(strncmp(bol, string, strlen(string)))
1800  {
1801  error = 1;
1802  }
1803  }
1804  break;
1805  }
1806  default:
1807  {
1808  string = "NEWSGROUPS:FULL";
1809  if(!strncmp(bol, string, strlen(string)))
1810  {
1811  /* Newsgroups header field detected */
1812  log_add(server[handle].lfs,
1813  "[Newsgroups header field "
1814  "available from overview]\n");
1815  newsgroups_available = field;
1816  }
1817  break;
1818  }
1819  }
1820  if(error)
1821  {
1822  res = -1;
1823  break;
1824  }
1825 
1826  /* Skip to start of next line */
1827  bol = strchr(bol, (int) '\n');
1828  if (NULL == bol)
1829  {
1830  /* There are 7 mandatory fields after article number */
1831  if(7 > field) { res = -1; }
1832  break;
1833  }
1834  else
1835  {
1836  ++bol;
1837  ++field;
1838  }
1839  }
1840  }
1841  }
1842  }
1843  /* Release memory for response */
1844  destroy_response(&response);
1845  }
1846 
1847  /* Store index of Newsgroups header field in overview */
1848  if (!res)
1849  {
1850  server[handle].over_newsgroups = newsgroups_available;
1851  }
1852 
1853  return(res);
1854 }
1855 
1856 
1857 /* ========================================================================== */
1858 /* Ensure that NNTP server is in READER mode */
1859 
1860 static int switch_mode_reader(int handle)
1861 {
1862  int res = 0;
1863  struct nntp_response* response = NULL;
1864 
1865  if(NNTP_CAPA_MODE_READER & server[handle].capabilities)
1866  {
1867  /* Allocate memory for response */
1868  res = create_response(&response);
1869  if(!res)
1870  {
1871  res = exec_cmd(handle, NNTP_CMD_MODE_READER, response);
1872  if (!res)
1873  {
1874  /* Refresh capabilities of server */
1875  res = refresh_capabilities(handle);
1876  if(!(NNTP_CAPA_READER & server[handle].capabilities))
1877  {
1878  /* Switch to reading mode failed */
1879  PRINT_ERROR("Protocol error: No READER capability "
1880  "after (advertised) mode switch");
1881  res = -1;
1882  }
1883  }
1884  }
1885  /* Release memory for response */
1886  destroy_response(&response);
1887  }
1888 
1889  return(res);
1890 }
1891 
1892 
1893 /* ========================================================================== */
1894 /* Negotiate maximum supported article number (MAXARTNUM extension) */
1895 
1896 static int negotiate_maxartnum(int handle)
1897 {
1898  int res = 0;
1899  struct nntp_response* response = NULL;
1900  nntp_anum_t maxartnum = NNTP_ANUM_T_MAX;
1901  char argument[NNTP_ARGLENGTHMAX + (size_t) 1];
1902  int rv;
1903 
1904  if(NNTP_CAPA_MAXARTNUM & server[handle].capabilities)
1905  {
1906  /* Create string with our maximum supported article number */
1907 #if ULONG_MAX < NNTP_ANUM_T_MAX
1908  PRINT_ERROR("Maximum article number does not fit in largest data type");
1909  res = -1;
1910 #else
1911  if (maxartnum > server[handle].maxartnum)
1912  {
1913  maxartnum = server[handle].maxartnum;
1914  }
1915  rv = posix_snprintf(argument, NNTP_ARGLENGTHMAX + (size_t) 1,
1916  "%lu", (unsigned long int) maxartnum);
1917  if (0 > rv || NNTP_ARGLENGTHMAX < (size_t) rv)
1918  {
1919  PRINT_ERROR("Maximum article number string generation failed");
1920  res = -1;
1921  }
1922 #endif
1923  if (!res)
1924  {
1925  /* Allocate memory for response */
1926  res = create_response(&response);
1927  if(!res)
1928  {
1929  res = exec_cmd(handle, NNTP_CMD_MAXARTNUM, response, argument);
1930  if (!res)
1931  {
1932  if(2U != response->code1)
1933  {
1934  /* Command was not successful */
1935  PRINT_ERROR("Maximum article number negotiation failed");
1936  res = -1;
1937  }
1938  else
1939  {
1940  /* Refresh capabilities of server */
1941  res = refresh_capabilities(handle);
1942  if(NNTP_CAPA_MAXARTNUM & server[handle].capabilities)
1943  {
1944  /* Switch to reading mode failed */
1945  PRINT_ERROR("Protocol error: MAXARTNUM capability still "
1946  "advertised after negotiation");
1947  res = -1;
1948  }
1949  }
1950  }
1951  }
1952  /* Release memory for response */
1953  destroy_response(&response);
1954  }
1955  }
1956 
1957  return(res);
1958 }
1959 
1960 
1961 /* ========================================================================== */
1962 /* Get distribution patterns */
1963 
1964 static int get_distrib_pats(int handle)
1965 {
1966  int res = 0;
1967  struct nntp_response* response = NULL;
1968 
1969  /* Destroy old data */
1970  server[handle].distrib_pats_len = 0;
1971  if(NULL != server[handle].distrib_pats)
1972  {
1973  posix_free((void*) server[handle].distrib_pats);
1974  }
1975  server[handle].distrib_pats = NULL;
1976 
1977  /* Return success if not available */
1978  if(NNTP_CAPA_LIST_DISTRIB_PATS & server[handle].capabilities)
1979  {
1980  /* Allocate memory for response */
1981  res = create_response(&response);
1982 
1983  /* Send LIST DISTRIB.PATS command */
1984  if(!res)
1985  {
1986  res = exec_cmd(handle, NNTP_CMD_LIST_DISTRIB_PATS, response);
1987  }
1988  if(!res)
1989  {
1990  if(2U != response->code1)
1991  {
1992  /* Command was not successful */
1993  res = -1;
1994  if(503U == response->status)
1995  {
1996  PRINT_ERROR("Server reported that no distribution patterns"
1997  " are maintained");
1998  /* NULL pointer and nonzero length indicate "not maintained" */
1999  server[handle].distrib_pats_len = 1;
2000  res = 0;
2001  }
2002  }
2003  else
2004  {
2005  /* Command was successful */
2006  server[handle].distrib_pats_len = response->bufsize;
2007  server[handle].distrib_pats = response->content;
2008  /* Preserve content buffer */
2009  response->content = NULL;
2010  }
2011  }
2012 
2013  /* Release memory for response */
2014  destroy_response(&response);
2015  }
2016 
2017  return(res);
2018 }
2019 
2020 
2021 /* ========================================================================== */
2022 /*! \brief Open connection to NNTP server
2023  *
2024  * \param[out] handle Pointer to server descriptor
2025  * \param[in] servername Server name or address string
2026  * \param[in] service Service name or port
2027  * \param[in] logfile Logfile name
2028  * \param[in] enc Encryption algorithm ID (for connection)
2029  * \param[in] auth Authentication algorithm ID (client against server)
2030  *
2031  * \e servername must be an IPv4 address in dotted decimal representation, an
2032  * IPv6 address in colon separated hexadecimal representation or a hostname that
2033  * can be resolved to an IPv4 address via DNS.
2034  *
2035  * \e service must be a valid service name like \c nntp or \c nntps or it must
2036  * be the target TCP port on which the NNTP or NNTPS service is listening. The
2037  * default value is 119 (or 563 for NNTP over TLS respectively).
2038  *
2039  * If \e logfile is \c NULL then log data is redirected to \c /dev/null .
2040  *
2041  * This function handles optional encryption for the connection to the server if
2042  * \e enc is nonzero. The following encryption algorithms are supported:
2043  * - 0: No encryption
2044  * - 1: RFC 2246 / RFC 4346 / RFC 5246 / RFC 8446 conformant TLS
2045  * <br>This includes the authentication of the server against the client
2046  * using a X509 certificate.
2047  * - 2: Same as 1 but additionally offer weak cipher suites to server
2048  * <br>The list includes (at the end) the cipher suite RSA-RSA-RC4-MD5 that
2049  * was defined as mandatory by RFC 4642 in the past.
2050  * <br>Not recommended! Use only if algorithm 1 is not accepted by server.
2051  *
2052  * This function handles optional authentication of the client against the
2053  * server if \e auth is nonzero. The following authentication algorithms are
2054  * supported:
2055  * - 0: No authentication
2056  * - 1: RFC 4643 conformant AUTHINFO USER/PASS authentication
2057  * <br>An encrypted connection is mandatory for this algorithm!
2058  * <br>The first optional parameter is a flag of type \c int that indicates
2059  * whether authentication should be done immediately.
2060  * <br>There must be two additional string parameters of type \c const
2061  * \c char* that specify username and password as plain text. Both strings
2062  * are copied and the pointers are not required to stay valid after this
2063  * function returns.
2064  *
2065  * \return
2066  * - 0 on success (a valid connection handle was written to \e handle )
2067  * - Negative value on error
2068  */
2069 
2070 int nntp_open(int* handle, const char* servername, const char* service,
2071  const char* logfile, int enc, int auth, ...)
2072 {
2073  va_list ap; /* Object for argument list handling */
2074  int res = 0;
2075  int af = POSIX_AF_UNSPEC; /* Network address family */
2076  struct nntp_response* response = NULL;
2077  char sbuf[7];
2078  FILE* fs;
2079 #if CFG_USE_TLS || !CFG_CMPR_DISABLE
2080  int rv;
2081 #endif /* CFG_USE_TLS || !CFG_CMPR_DISABLE */
2082 #if CFG_USE_TLS
2083  int weak = 0; /* Offer weak/unsecure cipher suites to server */
2084  const char* sni = NULL; /* Hostname for "server_name" TLS extension */
2085  const char* pv; /* Protocol version string */
2086  const char* cs; /* Cipher suite string */
2087  const char* kx; /* Key exchange (not part of cipher suite in TLSv1.3) */
2088  void* cert; /* Pointer to certificate object (from server) */
2089  const char* certs; /* Certificate string (from server) */
2090  int immed = 0;
2091  const char* s1;
2092  char* s2;
2093  size_t len;
2094 #endif /* CFG_USE_TLS */
2095 
2096  *handle = -1;
2097  va_start(ap, auth);
2098 
2099  /* Sanity checks */
2100  if(0 > enc || 2 < enc)
2101  {
2102  PRINT_ERROR("Requested encryption algorithm not supported");
2103  res = -1;
2104  }
2105  else if(0 > auth || 1 < auth)
2106  {
2107  PRINT_ERROR("Requested authentication algorithm not supported");
2108  res = -1;
2109  }
2110 #if !CFG_NNTP_AUTH_UNENCRYPTED
2111  else if(1 == auth && !enc)
2112  {
2113  PRINT_ERROR("Encryption required for selected authentication algorithm");
2114  res = -1;
2115  }
2116 #endif
2117 
2118  /* Allocate memory for response */
2119  if(!res) { res = create_response(&response); }
2120 
2121  /* Allocate connection object */
2122  if(!res)
2123  {
2124  res = get_handle(handle);
2125  if(!res)
2126  {
2127  server[*handle].auth = auth;
2128 #if CFG_USE_TLS
2129  if(1 == auth)
2130  {
2131  /* Get immediate flag */
2132  immed = va_arg(ap, int);
2133  /* Copy user and password to server handle */
2134  s1 = va_arg(ap, const char*);
2135  len = strlen(s1);
2136  s2 = (char*) posix_malloc(++len);
2137  if(NULL == s2) { res = -1; }
2138  else
2139  {
2140  strncpy(s2, s1, len);
2141  server[*handle].user = s2;
2142  s1 = va_arg(ap, const char*);
2143  len = strlen(s1);
2144  s2 = (char*) posix_malloc(++len);
2145  if(NULL == s2) { res = -1; }
2146  else
2147  {
2148  strncpy(s2, s1, len);
2149  server[*handle].passwd = s2;
2150  }
2151  }
2152  }
2153 #endif /* CFG_USE_TLS */
2154  }
2155  }
2156 
2157  /* Open logfile */
2158  if(!res)
2159  {
2160  res = log_open_logfile(&fs, logfile);
2161  if(!res) { server[*handle].lfs = fs; }
2162  }
2163 
2164  /* Connect to server */
2165  if(!res)
2166  {
2167  log_add(server[*handle].lfs, "[Using NNTP protocol driver]\n");
2168  log_add(server[*handle].lfs, "[=> Connect to ");
2169  log_add(server[*handle].lfs, servername);
2170  posix_snprintf(sbuf, 7, ":%s", service);
2171  log_add(server[*handle].lfs, sbuf);
2172  log_add(server[*handle].lfs, "]\n");
2173  res = inet_connect(&server[*handle].sd, &af, servername, service);
2174  /* Set RX and TX timeouts if supported by OS */
2175  inet_set_rx_timeout(server[*handle].sd, 15U);
2176  inet_set_tx_timeout(server[*handle].sd, 15U);
2177  }
2178 
2179  /* Establish encrypted connection */
2180  if(!res && enc)
2181  {
2182 #if CFG_USE_TLS
2183  if(2 == enc) { weak = 1; }
2184  sni = tls_sni(servername);
2185  res = tls_open(server[*handle].sd, &server[*handle].eco, weak, sni);
2186  if(res)
2187  {
2188  PRINT_ERROR("Failed to establish encryption layer");
2189  }
2190  else
2191  {
2192  log_add(server[*handle].lfs, "[Established encrypted connection");
2193  rv = tls_get_ciphersuite(&server[*handle].eco, &pv, &cs, &kx);
2194  if(!rv)
2195  {
2196  log_add(server[*handle].lfs, " using ");
2197  log_add(server[*handle].lfs, pv);
2198  log_add(server[*handle].lfs, " protocol with cipher suite ");
2199  log_add(server[*handle].lfs, cs);
2200  }
2201  log_add(server[*handle].lfs, "]\n");
2202  }
2203  if(!res)
2204  {
2205  /* Verify certificate received from server */
2206  rv = tls_cert_verify(&server[*handle].eco, &cert, servername, weak);
2207  if(0 <= rv)
2208  {
2209  log_add(server[*handle].lfs, "[<= Expect X509 certificate]\n");
2210  if(!tls_cert_get_string(cert, &certs))
2211  {
2212  log_add(server[*handle].lfs, certs);
2213  tls_free((void*) certs);
2214  }
2215  }
2216  if(rv)
2217  {
2218  log_add(server[*handle].lfs,
2219  "[Server certificate verification failed]\n");
2220  if(-2 == rv) { res = rv; } else { res = -1; }
2221  }
2222  else
2223  {
2224  log_add(server[*handle].lfs,
2225  "[Server certificate verification successful]\n");
2226  }
2227  }
2228 #else /* CFG_USE_TLS */
2229  PRINT_ERROR("Compiled without TLS support");
2230  res = -1;
2231 #endif /* CFG_USE_TLS */
2232  }
2233 
2234  /* Receive reply */
2235  if(!res) { res = recv_reply(*handle, NNTP_CMD_INIT, response); }
2236 
2237  /* Release memory for response */
2238  destroy_response(&response);
2239 
2240  /* Get capabilities of server */
2241  if(!res) { res = refresh_capabilities(*handle); }
2242 
2243  /* Switch server to READER mode */
2244  if(!res) { res = switch_mode_reader(*handle); }
2245 
2246  /* Check for error */
2247  if(res)
2248  {
2249  if(-2 == res)
2250  {
2251  /* TLS module has requested to close the connection */
2252  PRINT_ERROR("TLS module has requested to close the connection");
2253  }
2254  else { PRINT_ERROR("Cannot connect to server"); }
2255  nntp_close(handle, NNTP_CLOSE_NOQUIT);
2256  }
2257  else
2258  {
2259  server[*handle].connected = 1;
2260 
2261 #if CFG_USE_TLS
2262  /* Do authentication immediately on request */
2263  if(1 == auth && immed)
2264  {
2265  if(exec_auth(*handle))
2266  {
2267  /* Authentication failed */
2268  log_add(server[*handle].lfs, "[Authentication failed]\n");
2269  res = -3;
2270  }
2271  else { res = refresh_capabilities(*handle); }
2272  }
2273 #endif /* CFG_USE_TLS */
2274 
2275  /* Negotiate maximum article number (continue after error) */
2276  if(!res) { (void) negotiate_maxartnum(*handle); }
2277 
2278  /* Don't enable compression if deferred authentication is configured */
2279  if(!res && 0 >= server[*handle].auth)
2280  {
2281  /* Enable compression if supported by server */
2282  if(server[*handle].capabilities & NNTP_CAPA_COMPRESS)
2283  {
2284 #if !CFG_CMPR_DISABLE
2285  /* Check that negotiation is not disabled by user */
2286  if(config[CONF_COMPRESSION].val.i)
2287  {
2288  rv = exec_compress(*handle);
2289  if(0 < rv)
2290  {
2291  /* Compression algorithm negotiation failed */
2292  log_add(server[*handle].lfs,
2293  "[No matching compression algorithm available]\n");
2294  }
2295  if(rv)
2296  {
2297  /* Enabling compression failed */
2298  log_add(server[*handle].lfs, "[Enabling compression failed]\n");
2299  }
2300  else { res = refresh_capabilities(*handle); }
2301  /* Ignore errors and continue without compression */
2302  }
2303  else
2304  {
2305  log_add(server[*handle].lfs,
2306  "[Compression negotiation disabled by user]\n");
2307  }
2308 #else /* !CFG_CMPR_DISABLE */
2309  log_add(server[*handle].lfs,
2310  "[Compression disabled by configuration]\n");
2311 #endif /* !CFG_CMPR_DISABLE */
2312  }
2313  }
2314  }
2315 
2316  /* Get overview format (only once with the current implementation) */
2317  if(!res)
2318  {
2319  res = refresh_overview_format(*handle);
2320  if(res)
2321  {
2322  PRINT_ERROR("Reading overview format failed of format invalid");
2323  }
2324  }
2325 
2326  /* Get distribution patterns */
2327  if(!res)
2328  {
2329  if(get_distrib_pats(*handle))
2330  {
2331  PRINT_ERROR("Reading distribution patterns failed");
2332  }
2333  }
2334 
2335  va_end(ap);
2336 
2337  return(res);
2338 }
2339 
2340 
2341 /* ========================================================================== */
2342 /*! \brief Disconnect from NNTP server
2343  *
2344  * \param[in,out] handle Pointer to server descriptor
2345  * \param[in] flags Control flags (use \c NNTP_CLOSE_xxx constants)
2346  */
2347 
2348 void nntp_close(int* handle, unsigned int flags)
2349 {
2350  int rv;
2351  struct nntp_response* response = NULL;
2352 
2353  if(-1 != *handle)
2354  {
2355  /* Send QUIT command (if not disabled) */
2356  if(!(NNTP_CLOSE_NOQUIT & flags))
2357  {
2358  /* This will automatically exec a termination flush for compression */
2359  rv = create_response(&response);
2360  if(!rv) { exec_cmd(*handle, NNTP_CMD_QUIT, response); }
2361  destroy_response(&response);
2362  }
2363 #if !CFG_CMPR_DISABLE
2364  /* Shutdown compression layer */
2365  if(server[*handle].compress_active)
2366  {
2367  /* Destroy compressed data stream object */
2368  cmpr_stream_destructor(server[*handle].compress_stream);
2369  log_add(server[*handle].lfs, "[Compression layer terminated]\n");
2370  }
2371 #endif /* !CFG_CMPR_DISABLE */
2372 #if CFG_USE_TLS
2373  /* Shutdown encryption layer */
2374  if(NULL != server[*handle].eco)
2375  {
2376  tls_close(&server[*handle].eco);
2377  log_add(server[*handle].lfs, "[Encryption layer terminated]\n");
2378  }
2379  posix_free((void*) server[*handle].user);
2380  posix_free((void*) server[*handle].passwd);
2381 # if CFG_USE_OPENSSL_API_1_1 && !CFG_USE_OPENSSL_API_3
2382  posix_free((void*) server[*handle].peekbuf);
2383 # endif /* CFG_USE_OPENSSL_API_1_1 && !CFG_USE_OPENSSL_API_3 */
2384 #endif /* CFG_USE_TLS */
2385  /* Destroy distribution patterns */
2386  posix_free((void*) server[*handle].distrib_pats);
2387  /* Close logfile */
2388  if(NULL != server[*handle].lfs)
2389  {
2390  log_add(server[*handle].lfs, "[Close connection]\n\n");
2391  log_close_logfile(&server[*handle].lfs);
2392  }
2393  /* Terminate network connection */
2394  if(-1 != server[*handle].sd) { inet_close(&server[*handle].sd); }
2395  server[*handle].connected = 0;
2396  *handle = -1;
2397  }
2398 
2399  return;
2400 }
2401 
2402 
2403 /* ========================================================================== */
2404 /*! \brief Get message of the day capability of NNTP server
2405  *
2406  * \param[in] handle Server descriptor
2407  *
2408  * This function will trigger no communication with the server. It simply
2409  * reports the currently buffered state for the server associated with
2410  * \e handle .
2411  *
2412  * \return
2413  * - \c LIST MOTD capability flag (nonzero if capability is available)
2414  */
2415 
2417 {
2418  int res = 0;
2419 
2420  if(-1 != handle)
2421  {
2422  if(server[handle].capabilities & NNTP_CAPA_LIST_MOTD) { res = 1; }
2423  }
2424 
2425  return(res);
2426 }
2427 
2428 
2429 /* ========================================================================== */
2430 /*! \brief Get message of the day
2431  *
2432  * This option is defined in RFC 6048.
2433  *
2434  * \param[in] handle Server descriptor
2435  * \param[out] data Pointer to MOTD data buffer pointer
2436  * \param[out] len Pointer to buffer size
2437  *
2438  * On success, the caller is responsible for releasing the memory allocated
2439  * for the array \e data .
2440  *
2441  * \return
2442  * - 0 on success
2443  * - -1 on error
2444  * - -2 if connection to server is broken
2445  * - -3 if authentication requested by server failed
2446  */
2447 
2448 int nntp_get_motd(int handle, char** data, size_t* len)
2449 {
2450  int res;
2451  struct nntp_response* response = NULL;
2452 
2453  /* Allocate memory for response */
2454  res = create_response(&response);
2455 
2456  /* Send LIST MOTD command */
2457  if(!res) { res = exec_cmd(handle, NNTP_CMD_LIST_MOTD, response); }
2458  if(!res)
2459  {
2460  if(2U != response->code1)
2461  {
2462  if(503U == response->status)
2463  {
2464  PRINT_ERROR("Server reported that no MOTD is maintained");
2465  }
2466  res = -1;
2467  }
2468  else
2469  {
2470  /* Command wass successful */
2471  *len = response->bufsize;
2472  *data = response->content;
2473  /* Preserve content buffer */
2474  response->content = NULL;
2475  }
2476  }
2477 
2478  /* Release memory for response */
2479  destroy_response(&response);
2480 
2481  return(res);
2482 }
2483 
2484 
2485 /* ========================================================================== */
2486 /*! \brief Get distribution patterns
2487  *
2488  * This option is defined in RFC 3977.
2489  *
2490  * \param[in] handle Server descriptor
2491  * \param[out] data Pointer to pattern data buffer
2492  * \param[out] len Pointer to buffer size
2493  *
2494  * \note
2495  * It is allowed to pass \c NULL for \e len if the caller is not interested in
2496  * this value.
2497  *
2498  * This function will trigger no communication with the server. On success it
2499  * returns a pointer to the currently buffered data for the server associated
2500  * with \e handle that was retrieved while opening the connection.
2501  *
2502  * \note
2503  * No extra memory is allocated for the caller, so there is nothing to free.
2504  *
2505  * \return
2506  * - 0 on success
2507  * - 1 if not available
2508  * - -1 on error
2509  */
2510 
2511 int nntp_get_distrib_pats(int handle, const char** data, size_t* len)
2512 {
2513  int res = 1;
2514 
2515  *data = NULL;
2516  if(NULL != len) { *len = 0; }
2517  if(server[handle].capabilities & NNTP_CAPA_LIST_DISTRIB_PATS)
2518  {
2519  res = -1;
2520  if(server[handle].distrib_pats_len)
2521  {
2522  /* NULL pointer with nonzero length indicates "not maintained */
2523  if(NULL == server[handle].distrib_pats) { res = 1; }
2524  else
2525  {
2526  *data = server[handle].distrib_pats;
2527  if(NULL != len) { *len = server[handle].distrib_pats_len; }
2528  res = 0;
2529  }
2530  }
2531  }
2532 
2533  return(res);
2534 }
2535 
2536 
2537 /* ========================================================================== */
2538 /*! \brief Get message of the day capability of NNTP server
2539  *
2540  * \param[in] handle Server descriptor
2541  *
2542  * This function will trigger no communication with the server. It simply
2543  * reports the currently buffered state for the server associated with
2544  * \e handle .
2545  *
2546  * \return
2547  * - \c LIST SUBSCRIPTIONS capability flag (nonzero if capability is available)
2548  */
2549 
2551 {
2552  int res = 0;
2553 
2554  if(-1 != handle)
2555  {
2556  if(server[handle].capabilities & NNTP_CAPA_LIST_SUBSCRIPTIONS)
2557  {
2558  res = 1;
2559  }
2560  }
2561 
2562  return(res);
2563 }
2564 
2565 
2566 /* ========================================================================== */
2567 /*! \brief Get subscription proposals
2568  *
2569  * This option is defined in RFC 6048.
2570  *
2571  * \param[in] handle Server descriptor
2572  * \param[out] data Pointer to subscriptions data buffer pointer
2573  * \param[out] len Pointer to buffer size
2574  *
2575  * On success, the caller is responsible for releasing the memory allocated
2576  * for the array \e data .
2577  *
2578  * \return
2579  * - 0 on success
2580  * - -1 on error
2581  * - -2 if connection to server is broken
2582  * - -3 if authentication requested by server failed
2583  */
2584 
2585 int nntp_get_subscriptions(int handle, char** data, size_t* len)
2586 {
2587  int res;
2588  struct nntp_response* response = NULL;
2589 
2590  /* Allocate memory for response */
2591  res = create_response(&response);
2592 
2593  /* Send LIST SUBSCRIPTIONS command */
2594  if(!res) { res = exec_cmd(handle, NNTP_CMD_LIST_SUBSCRIPTIONS, response); }
2595  if(!res)
2596  {
2597  if(2U != response->code1)
2598  {
2599  if(503U == response->status)
2600  {
2601  PRINT_ERROR("Server reported that no SUBSCRIPTIONS are maintained");
2602  }
2603  res = -1;
2604  }
2605  else
2606  {
2607  /* Command wass successful */
2608  *len = response->bufsize;
2609  *data = response->content;
2610  /* Preserve content buffer */
2611  response->content = NULL;
2612  }
2613  }
2614 
2615  /* Release memory for response */
2616  destroy_response(&response);
2617 
2618  return(res);
2619 }
2620 
2621 
2622 /* ========================================================================== */
2623 /*! \brief Allocate and initialize a descriptor for group
2624  *
2625  * \param[in] name Group name
2626  *
2627  * An empty group descriptor is created and \e name is copied behind this
2628  * descriptor so that the whole object can be destroyed as a single memory
2629  * block.
2630  *
2631  * On success, the caller is responsible to destroy the allocated object.
2632  *
2633  * \return
2634  * - Pointer to initialized group descriptor on success
2635  * - NULL on error
2636  */
2637 
2639 {
2640  struct nntp_groupdesc* gd = NULL;
2641  size_t l;
2642  size_t ls;
2643 
2644  /* Allocate memory for group descriptor */
2645  l = sizeof(struct nntp_groupdesc);
2646  ls = strlen(name) + (size_t) 1;
2647  gd = (struct nntp_groupdesc*) posix_malloc(l + ls);
2648  if (NULL != gd)
2649  {
2650  /* Copy group name to descriptor */
2651  strcpy(&((char*) gd)[l], name);
2652  gd->name = &((char*) gd)[l];
2653  gd->eac = 0;
2654  gd->lwm = 0;
2655  gd->hwm = 0;
2656  gd->flags = 0;
2657  }
2658 
2659  return(gd);
2660 }
2661 
2662 
2663 /* ========================================================================== */
2664 /*! \brief Get group list
2665  *
2666  * \param[in] handle Server descriptor
2667  * \param[out] groupcount Pointer to number of groups
2668  * \param[out] p Pointer to array of group descriptors
2669  *
2670  * On success, the caller is responsible for releasing the memory allocated
2671  * for the array \e p .
2672  *
2673  * \note
2674  * If \e groupcount is zero, \e p may be \c NULL .
2675  *
2676  * \return
2677  * - 0 on success
2678  * - -1 on error
2679  * - -2 if connection to server is broken
2680  * - -3 if authentication requested by server failed
2681  */
2682 
2683 int nntp_get_grouplist(int handle, size_t* groupcount,
2684  struct nntp_groupdesc** p)
2685 {
2686  int res;
2687  struct nntp_response* response = NULL;
2688  char* content = NULL;
2689  int ascii;
2690  int field;
2691  size_t name;
2692  nntp_anum_t n;
2693  nntp_anum_t lwm;
2694  nntp_anum_t hwm;
2695  int pa; /* Posting allowed flag */
2696  size_t i = 0;
2697  int invalid;
2698  int c;
2699  char wm[16];
2700  int wm_len;
2701  int wm_flag;
2702  size_t gi = 0;
2703  int rv;
2704 
2705  /* Init values returned to caller */
2706  *groupcount = 0;
2707  *p = NULL;
2708 
2709  /* Allocate memory for response */
2710  res = create_response(&response);
2711 
2712  /* Send LIST [ACTIVE] command */
2713  if(!res)
2714  {
2715  res = exec_cmd(handle, NNTP_CMD_LIST, response);
2716  /* Verify that list is not empty */
2717  if (!res && NULL == response->content) { res = -1; }
2718  }
2719 
2720  /* Allocate memory for group descriptors and group name strings */
2721  if(!res)
2722  {
2723  /* printf("Lines: %u\n", (unsigned int) response->lines); */
2724  *p = (struct nntp_groupdesc*) posix_malloc(sizeof(struct nntp_groupdesc)
2725  * response->lines
2726  + response->bufsize);
2727  if (NULL == *p)
2728  {
2729  PRINT_ERROR("Memory allocation for group list failed");
2730  res = -1;
2731  }
2732  else
2733  {
2734  /* Append content after group descriptor table */
2735  content = (void*) &(*p)[response->lines];
2736  memcpy((void*) content, (void*) response->content, response->bufsize);
2737  }
2738  }
2739 
2740  /* Release memory for response */
2741  destroy_response(&response);
2742 
2743  /* Parse response data */
2744  if(!res)
2745  {
2746  /*
2747  * The response data has the following format for every line:
2748  *
2749  * <group name> <high water mark> <low water mark> <status>
2750  *
2751  * An empty response is valid.
2752  * The fields are separated by one or more spaces.
2753  * The water mark numbers may contain leading zeros.
2754  *
2755  * A status 'n' is taken as "posting is not allowed", all other status
2756  * indications are taken as "posting allowed" (try at least).
2757  * Invalid lines are ignored and the processing is not aborted.
2758  */
2759 
2760  /* Response parser */
2761  while(content[i])
2762  {
2763  /* Parse next line */
2764  invalid = 1;
2765  field = 0;
2766  name = i;
2767  ascii = 1;
2768  wm_len = 0;
2769  wm_flag = 0;
2770  lwm = 0;
2771  hwm = 0;
2772  pa = 1;
2773  do
2774  {
2775  /* Get next character */
2776  c = (int) content[i];
2777  if(!c) { break; }
2778  /* Field parser state machine */
2779  switch(field)
2780  {
2781  case 0: /* Group name */
2782  {
2783  /* Check for end of field */
2784  if(0x20 == c)
2785  {
2786  /* Terminate group name string */
2787  content[i] = 0;
2788  /* Check whether group name is ASCII encoded */
2789  if(enc_ascii_check_printable(&content[name]))
2790  {
2791  /* Treat nonprintable ASCII characters as Unicode */
2792  ascii = 0;
2793  }
2794  /* Check whether name encoding is valid UTF-8 */
2795  if(!ascii)
2796  {
2797  if(enc_uc_check_utf8(&content[name]))
2798  {
2799  /* Name field encoding is invalid */
2800  PRINT_ERROR("Unicode group name invalid");
2801  field = -1;
2802  break;
2803  }
2804  }
2805  ++field;
2806  }
2807  break;
2808  }
2809  case 1: /* Potential additional spaces */
2810  {
2811  if(0x20 == c) { break; }
2812  ++field;
2813  /* No break here is intended */
2814  }
2815  case 2: /* Water mark */
2816  {
2817  rv = parse_number(c, wm, &wm_len, &n);
2818  if(-1 == rv) { field = -1; break; }
2819  if(!rv)
2820  {
2821  if(!wm_flag)
2822  {
2823  hwm = n;
2824  wm_flag = 1;
2825  wm_len = 0;
2826  field = 1;
2827  }
2828  else
2829  {
2830  lwm = n;
2831  ++field;
2832  }
2833  }
2834  break;
2835  }
2836  case 3: /* Potential additional spaces */
2837  {
2838  if(0x20 == c) { break; }
2839  ++field;
2840  /* No break here is intended */
2841  }
2842  case 4: /* Status */
2843  {
2844  /* Check whether posting into this group is allowed */
2845  if(invalid)
2846  {
2847  if('n' == c) pa = 0;
2848  /* Mark line as valid */
2849  invalid = 0;
2850  }
2851  break;
2852  }
2853  default:
2854  {
2855  /* Error */
2856  invalid = 1;
2857  break;
2858  }
2859  }
2860  }
2861  while((char) 0x0A != content[i++]);
2862 
2863  /* Store data */
2864  if(invalid)
2865  {
2866  if (!gi && !content[i])
2867  {
2868  /* Empty list */
2869  PRINT_ERROR("Empty group list received");
2870  }
2871  else
2872  {
2873  /* Invalid group entry found */
2874  PRINT_ERROR("Unsupported entry in group list ignored");
2875  }
2876  }
2877  else
2878  {
2879 #if 0
2880  /* For debugging only */
2881  printf("%lu: %s %lu %lu %d\n", (unsigned long int) gi,
2882  &content[name], hwm, lwm, pa);
2883 #endif
2884  /* Store group name */
2885  (*p)[gi].name = &content[name];
2886  /* Store watermarks */
2887  (*p)[gi].lwm = lwm;
2888  (*p)[gi].hwm = hwm;
2889  /* Store estimated article count */
2890  (*p)[gi].eac = 0;
2891  if(hwm > lwm) { (*p)[gi].eac = hwm - lwm + (nntp_anum_t) 1; }
2892  /* Store group flags */
2893  (*p)[gi].flags = 0;
2894  if(ascii) { (*p)[gi].flags |= NNTP_GROUP_FLAG_ASCII; }
2895  if(pa) { (*p)[gi].flags |= NNTP_GROUP_FLAG_PA; }
2896  /* Switch to next group */
2897  ++gi;
2898  }
2899  }
2900 
2901  /* Store group count */
2902  *groupcount = gi;
2903  }
2904 
2905  /* Before returning error: Release memory for group descriptors */
2906  if(res)
2907  {
2908  posix_free(*p);
2909  *p = NULL;
2910  *groupcount = 0;
2911  }
2912 
2913  return(res);
2914 }
2915 
2916 
2917 /* ========================================================================== */
2918 /*! \brief Get additional group information
2919  *
2920  * \param[in] handle Server descriptor
2921  * \param[out] groupcount Pointer to number of groups
2922  * \param[out] p Pointer to array of group descriptors
2923  *
2924  * On success, the caller is responsible for releasing the memory allocated
2925  * for the array \e p .
2926  *
2927  * \note
2928  * If \e groupcount is zero, \e p may be \c NULL .
2929  *
2930  * \return
2931  * - 0 on success
2932  * - -1 on error
2933  * - -2 if connection to server is broken
2934  * - -3 if authentication requested by server failed
2935  */
2936 
2937 int nntp_get_group_labels(int handle, size_t* groupcount,
2938  struct nntp_grouplabel** p)
2939 {
2940  int res;
2941  struct nntp_response* response = NULL;
2942  char* content = NULL;
2943  int field;
2944  size_t name;
2945  size_t label = 0;
2946  size_t i = 0;
2947  int invalid;
2948  int c;
2949  size_t gi = 0;
2950 
2951  /* Init values returned to caller */
2952  *groupcount = 0;
2953  *p = NULL;
2954 
2955  /* Allocate memory for response */
2956  res = create_response(&response);
2957 
2958  /* Send LIST NEWSGROUPS command */
2959  if(!res) { res = exec_cmd(handle, NNTP_CMD_LIST_NEWSGROUPS, response); }
2960 
2961  /* Check response */
2962  if(!res)
2963  {
2964  if(2U != response->code1)
2965  {
2966  if(503U == response->status)
2967  {
2968  PRINT_ERROR("Server reported that descriptions are not maintained");
2969  }
2970  res = -1;
2971  }
2972  }
2973  /* Verify that list is not empty */
2974  if (!res && NULL == response->content) { res = -1; }
2975 
2976  /* Allocate memory for group descriptors and group name strings */
2977  if(!res)
2978  {
2979  /* printf("Lines: %u\n", (unsigned int) response->lines); */
2980  *p = (struct nntp_grouplabel*) posix_malloc(sizeof(struct nntp_grouplabel)
2981  * response->lines
2982  + response->bufsize);
2983  if (NULL == *p)
2984  {
2985  PRINT_ERROR("Memory allocation for group information failed");
2986  res = -1;
2987  }
2988  else
2989  {
2990  /* Append content after group descriptor table */
2991  content = (void*) &(*p)[response->lines];
2992  memcpy((void*) content, (void*) response->content, response->bufsize);
2993  }
2994  }
2995 
2996  /* Release memory for response */
2997  destroy_response(&response);
2998 
2999  /* Parse response data */
3000  if(!res)
3001  {
3002  /*
3003  * The response data has the following format for every line:
3004  *
3005  * <group name> <group information>
3006  *
3007  * An empty response is valid.
3008  * The fields are separated by whitespace.
3009  */
3010 
3011  /* Response parser */
3012  while(content[i])
3013  {
3014  /* Parse next line */
3015  invalid = 1;
3016  field = 0;
3017  name = i;
3018  do
3019  {
3020  /* Get next character */
3021  c = (int) content[i];
3022  if(!c) { break; }
3023  /* Field parser state machine */
3024  switch(field)
3025  {
3026  case 0: /* Group name */
3027  {
3028  /* Check for end of field */
3029  if(0x20 == c || 0x09 == c)
3030  {
3031  /* Terminate group name string */
3032  content[i] = 0;
3033  ++field;
3034  }
3035  break;
3036  }
3037  case 1: /* Potential additional spaces */
3038  {
3039  if(0x20 == c || 0x09 == c) { break; };
3040  label = i;
3041  ++field;
3042  /* No break here is intended */
3043  }
3044  case 2: /* Group description */
3045  {
3046  /* Check for end of field */
3047  if(0x0D == c)
3048  {
3049  /* Terminate group name string */
3050  content[i] = 0;
3051  /* Mark line as valid */
3052  invalid = 0;
3053  }
3054  break;
3055  }
3056  default:
3057  {
3058  /* Error */
3059  invalid = 1;
3060  break;
3061  }
3062  }
3063  }
3064  while((char) 0x0A != content[i++]);
3065 
3066  /* Store data */
3067  if(invalid)
3068  {
3069  /* Invalid group entry found */
3070  PRINT_ERROR("Unsupported entry in group label list ignored");
3071  }
3072  else
3073  {
3074 #if 0
3075  /* For debugging only */
3076  printf("%lu: %s %s\n", (unsigned long int) gi,
3077  &content[name], &content[label]);
3078 #endif
3079  /* Store group name */
3080  (*p)[gi].name = &content[name];
3081  /* Store group information */
3082  (*p)[gi].label = &content[label];
3083  /* Switch to next group */
3084  ++gi;
3085  }
3086  }
3087 
3088  /* Store group count */
3089  *groupcount = gi;
3090  }
3091 
3092  /* Before returning error: Release memory for group descriptors */
3093  if(res)
3094  {
3095  posix_free(*p);
3096  *p = NULL;
3097  *groupcount = 0;
3098  }
3099 
3100  return(res);
3101 }
3102 
3103 
3104 /* ========================================================================== */
3105 /*! \brief Set current group
3106  *
3107  * \param[in] handle Server descriptor
3108  * \param[in] name Group name
3109  * \param[out] gd Pointer to group descriptor
3110  *
3111  * On success and if \e gd is not \c NULL , a pointer to an initialized group
3112  * descriptor for the group specified by \e name is written to \e gd .
3113  * The caller is responsible for releasing the memory allocated for this group
3114  * descriptor.
3115  *
3116  * \return
3117  * - 0 on success
3118  * - -1 on error
3119  * - -2 if connection to server is broken
3120  * - -3 if authentication requested by server failed
3121  */
3122 
3123 
3124 int nntp_set_group(int handle, const char* name, struct nntp_groupdesc** gd)
3125 {
3126  int res;
3127  struct nntp_response* response = NULL;
3128  char wm[16];
3129  int wm_len = 0;
3130  int wm_flag = 0;
3131  int field = 0;
3132  char* s;
3133  int rv;
3134  nntp_anum_t n;
3135  size_t i = 0;
3136  int c;
3137  int abort = 0;
3138  unsigned int flags = 0;
3139 
3140  if(NULL != gd) { *gd = NULL; }
3141 
3142  /* Allocate memory for response */
3143  res = create_response(&response);
3144 
3145  /* Check encoding of group name */
3146  if(!res)
3147  {
3148  if(enc_ascii_check(name))
3149  {
3150  if(enc_uc_check_utf8(name))
3151  {
3152  /* Name encoding is invalid */
3153  PRINT_ERROR("Unicode group name invalid");
3154  res = -1;
3155  }
3156  }
3157  else { flags |= NNTP_GROUP_FLAG_ASCII; }
3158  }
3159 
3160  /* Send GROUP command */
3161  if(!res) { res = exec_cmd(handle, NNTP_CMD_GROUP, response, name); }
3162 
3163  /* Check response */
3164  if(!res)
3165  {
3166  if(2U != response->code1)
3167  {
3168  PRINT_ERROR("Group not available");
3169  res = -1;
3170  }
3171  else if(NULL != gd)
3172  {
3173  /* Allocate memory and initialize group descriptor */
3175  if (NULL == *gd) { res = -1; }
3176  else
3177  {
3178  /* Copy flags to descriptor */
3179  (*gd)->flags = flags;
3180  /* Parse article count and watermarks */
3181  s = &response->msg[4];
3182  while(s[i] && !abort)
3183  {
3184  c = (int) s[i];
3185  if(!c) { break; }
3186  switch(field)
3187  {
3188  case 0: /* Potential additional spaces */
3189  {
3190  if(0x20 == c) { break; }
3191  ++field;
3192  /* No break here is intended */
3193  }
3194  case 1: /* Number of articles */
3195  {
3196  rv = parse_number(c, wm, &wm_len, &n);
3197  if(-1 == rv) { field = -1; break; }
3198  if(!rv)
3199  {
3200  (*gd)->eac = n;
3201  wm_len = 0;
3202  ++field;
3203  }
3204  break;
3205  }
3206  case 2: /* Potential additional spaces */
3207  {
3208  if(0x20 == c) { break; }
3209  ++field;
3210  /* No break here is intended */
3211  }
3212  case 3: /* Water marks */
3213  {
3214  rv = parse_number(c, wm, &wm_len, &n);
3215  if(-1 == rv) { field = -1; break; }
3216  if(!rv)
3217  {
3218  if(!wm_flag)
3219  {
3220  (*gd)->lwm = n;
3221  wm_flag = 1;
3222  wm_len = 0;
3223  field = 2;
3224  }
3225  else
3226  {
3227  (*gd)->hwm = n;
3228  if((*gd)->lwm > (*gd)->hwm)
3229  {
3230  if((*gd)->eac)
3231  {
3232  PRINT_ERROR("Invalid watermarks");
3233  (*gd)->eac = 0;
3234  }
3235  }
3236  /* Finished */
3237  abort = 1;
3238  }
3239  }
3240  break;
3241  }
3242  default:
3243  {
3244  /* Error */
3245  res = -1;
3246  abort = 1;
3247  break;
3248  }
3249  }
3250  ++i;
3251  }
3252  }
3253  }
3254  }
3255 
3256  /* Release memory for response */
3257  destroy_response(&response);
3258 
3259  /* Before returning error: Release memory for group descriptor */
3260  if(res && NULL != gd) { posix_free(*gd); *gd = NULL; }
3261 
3262  return(res);
3263 }
3264 
3265 
3266 /* ========================================================================== */
3267 /*! \brief Get overview capability of NNTP server
3268  *
3269  * \param[in] handle Server descriptor
3270  *
3271  * This function will trigger no communication with the server. It simply
3272  * reports the currently buffered state for the server associated with
3273  * \e handle .
3274  *
3275  * \return
3276  * - \c OVER capability flag (nonzero if this capability is available)
3277  */
3278 
3279 int nntp_get_capa_over(int handle)
3280 {
3281  int res = 0;
3282 
3283  if(-1 != handle)
3284  {
3285  if(server[handle].capabilities & NNTP_CAPA_OVER) { res = 1; }
3286  }
3287 
3288  return(res);
3289 }
3290 
3291 
3292 /* ========================================================================== */
3293 /*! \brief Get index of Newsgroups header field in overview
3294  *
3295  * \param[in] handle Server descriptor
3296  * \param[out] index Pointer to index
3297  *
3298  * This function will trigger no communication with the server. It simply
3299  * reports the currently buffered state for the server associated with
3300  * \e handle .
3301  *
3302  * The Newsgroups header field is optional for the overview. The index zero
3303  * is returned, if the overview does not contain the data.
3304  *
3305  * \return
3306  * - 0 on success (\e index is valid)
3307  * - -1 on error
3308  */
3309 
3310 int nntp_get_over_newsgroups_index(int handle, size_t* index)
3311 {
3312  size_t res = -1;
3313 
3314  if(-1 != handle)
3315  {
3316  *index = server[handle].over_newsgroups;
3317  res = 0;
3318  }
3319 
3320  return(res);
3321 }
3322 
3323 
3324 /* ========================================================================== */
3325 /*! \brief Get overview for article range
3326  *
3327  * \param[in] handle Server descriptor
3328  * \param[in] first First article identifier of range
3329  * \param[in] last Last article identifier of range
3330  * \param[out] data Pointer to overview data buffer pointer
3331  * \param[out] len Pointer to article buffer size
3332  *
3333  * On success, a pointer to the overview data buffer is written to \e data
3334  * and the caller is responsible to free the associated memory. The size of this
3335  * buffer is written to \e len .
3336  *
3337  * \return
3338  * - 0 on success
3339  * - -1 on error
3340  * - -2 if connection to server is broken
3341  * - -3 if authentication requested by server failed
3342  */
3343 
3344 int nntp_get_overview(int handle, nntp_anum_t first, nntp_anum_t last,
3345  char** data, size_t* len)
3346 {
3347  int res;
3348  struct nntp_response* response = NULL;
3349 
3350  /* Allocate memory for response */
3351  res = create_response(&response);
3352 
3353  /* Send OVER command */
3354  if(!res) { res = exec_cmd(handle, NNTP_CMD_OVER, response, &first, &last); }
3355  if(!res)
3356  {
3357  if(2U != response->code1) { res = -1; }
3358  else
3359  {
3360  /* Command wass successful */
3361  *len = response->bufsize;
3362  *data = response->content;
3363  /* Preserve content buffer */
3364  response->content = NULL;
3365  }
3366  }
3367 
3368  /* Release memory for response */
3369  destroy_response(&response);
3370 
3371  return(res);
3372 }
3373 
3374 
3375 /* ========================================================================== */
3376 /*! \brief Get complete article via Message-ID
3377  *
3378  * \param[in] handle Server descriptor
3379  * \param[in] mid Pointer to article Message-ID
3380  * \param[out] article Pointer to article buffer pointer
3381  * \param[out] len Pointer to article buffer size
3382  *
3383  * On success, a pointer to the article buffer is written to \e article
3384  * and the caller is responsible to free the associated memory. The size of this
3385  * buffer is written to \e len .
3386  *
3387  * \return
3388  * - 0 on success
3389  * - -1 on error
3390  * - -2 if connection to server is broken
3391  * - -3 if authentication requested by server failed
3392  */
3393 
3394 int nntp_get_article_by_mid(int handle, const char* mid,
3395  char** article, size_t* len)
3396 {
3397  int res;
3398  struct nntp_response* response = NULL;
3399 
3400  /* Allocate memory for response */
3401  res = create_response(&response);
3402 
3403  /* Send ARTICLE command */
3404  if(!res) { res = exec_cmd(handle, NNTP_CMD_ARTICLE_BY_MID, response, mid); }
3405  if(!res)
3406  {
3407  if(2U != response->code1) { res = -1; }
3408  else
3409  {
3410  /* Command wass successful */
3411  *len = response->bufsize;
3412  *article = response->content;
3413  /* Preserve content buffer */
3414  response->content = NULL;
3415  }
3416  }
3417 
3418  /* Release memory for response */
3419  destroy_response(&response);
3420 
3421  return(res);
3422 }
3423 
3424 
3425 /* ========================================================================== */
3426 /*! \brief Get complete article
3427  *
3428  * \param[in] handle Server descriptor
3429  * \param[in] id Pointer to article identifier
3430  * \param[out] article Pointer to article buffer pointer
3431  * \param[out] len Pointer to article buffer size
3432  *
3433  * On success, a pointer to the article buffer is written to \e article
3434  * and the caller is responsible to free the associated memory. The size of this
3435  * buffer is written to \e len .
3436  *
3437  * \return
3438  * - 0 on success
3439  * - -1 on error
3440  * - -2 if connection to server is broken
3441  * - -3 if authentication requested by server failed
3442  */
3443 
3444 int nntp_get_article(int handle, const nntp_anum_t* id, char** article,
3445  size_t* len)
3446 {
3447  int res;
3448  struct nntp_response* response = NULL;
3449 
3450  /* Allocate memory for response */
3451  res = create_response(&response);
3452 
3453  /* Send ARTICLE command */
3454  if(!res) { res = exec_cmd(handle, NNTP_CMD_ARTICLE, response, id); }
3455  if(!res)
3456  {
3457  if(2U != response->code1) { res = -1; }
3458  else
3459  {
3460  /* Command wass successful */
3461  *len = response->bufsize;
3462  *article = response->content;
3463  /* Preserve content buffer */
3464  response->content = NULL;
3465  }
3466  }
3467 
3468  /* Release memory for response */
3469  destroy_response(&response);
3470 
3471  return(res);
3472 }
3473 
3474 
3475 /* ========================================================================== */
3476 /*! \brief Get article header
3477  *
3478  * \param[in] handle Server descriptor
3479  * \param[in] id Pointer to article identifier
3480  * \param[out] header Pointer to article header buffer pointer
3481  * \param[out] len Pointer to article header buffer size
3482  *
3483  * On success, a pointer to the article header buffer is written to \e header
3484  * and the caller is responsible to free the associated memory. The size of this
3485  * buffer is written to \e len .
3486  *
3487  * \return
3488  * - 0 on success
3489  * - -1 on error
3490  * - -2 if connection to server is broken
3491  * - -3 if authentication requested by server failed
3492  */
3493 
3494 int nntp_get_article_header(int handle, const nntp_anum_t* id,
3495  char** header, size_t* len)
3496 {
3497  int res;
3498  struct nntp_response* response = NULL;
3499 
3500  /* Allocate memory for response */
3501  res = create_response(&response);
3502 
3503  /* Send HEAD command */
3504  if(!res) { res = exec_cmd(handle, NNTP_CMD_HEAD, response, id); }
3505  if(!res)
3506  {
3507  if(2U != response->code1) { res = -1; }
3508  else
3509  {
3510  /* Command wass successful */
3511  *len = response->bufsize;
3512  *header = response->content;
3513  /* Preserve content buffer */
3514  response->content = NULL;
3515  }
3516  }
3517 
3518  /* Release memory for response */
3519  destroy_response(&response);
3520 
3521  return(res);
3522 }
3523 
3524 
3525 /* ========================================================================== */
3526 /*! \brief Get article body
3527  *
3528  * \param[in] handle Server descriptor
3529  * \param[in] id Pointer to article identifier
3530  * \param[out] body Pointer to article body buffer pointer
3531  * \param[out] len Pointer to article body buffer size
3532  *
3533  * On success, a pointer to the article body buffer is written to \e body
3534  * and the caller is responsible to free the associated memory. The size of this
3535  * buffer is written to \e len .
3536  *
3537  * \return
3538  * - 0 on success
3539  * - -1 on error
3540  * - -2 if connection to server is broken
3541  * - -3 if authentication requested by server failed
3542  */
3543 
3544 int nntp_get_article_body(int handle, const nntp_anum_t* id, char** body,
3545  size_t* len)
3546 {
3547  int res;
3548  struct nntp_response* response = NULL;
3549 
3550  /* Allocate memory for response */
3551  res = create_response(&response);
3552 
3553  /* Send BODY command */
3554  if(!res) { res = exec_cmd(handle, NNTP_CMD_BODY, response, id); }
3555  if(!res)
3556  {
3557  if(2U != response->code1) { res = -1; }
3558  else
3559  {
3560  /* Command wass successful */
3561  *len = response->bufsize;
3562  *body = response->content;
3563  /* Preserve content buffer */
3564  response->content = NULL;
3565  }
3566  }
3567 
3568  /* Release memory for response */
3569  destroy_response(&response);
3570 
3571  return(res);
3572 }
3573 
3574 
3575 /* ========================================================================== */
3576 /*! \brief Post article
3577  *
3578  * \param[in] handle Server descriptor
3579  * \param[in] article Pointer to article buffer
3580  *
3581  * \e article must point to a RFC 5536 conformant article in canonical form.
3582  * The caller is responsible for the validity check of the article format.
3583  *
3584  * \return
3585  * - 0 on success
3586  * - -1 on error
3587  * - -2 if connection to server is broken
3588  * - -3 if authentication requested by server failed
3589  */
3590 
3591 int nntp_post_article(int handle, const char* article)
3592 {
3593  int res;
3594  struct nntp_response* response = NULL;
3595 
3596  /* Allocate memory for response */
3597  res = create_response(&response);
3598 
3599  /* Send POST command */
3600  if(!res) { res = exec_cmd(handle, NNTP_CMD_POST, response, article); }
3601  if(!res)
3602  {
3603  if(2U != response->code1) { res = -1; }
3604  }
3605 
3606  /* Release memory for response */
3607  destroy_response(&response);
3608 
3609  return(res);
3610 }
3611 
3612 
3613 /*! @} */
3614 
3615 /* EOF */
nntp_post_article
int nntp_post_article(int handle, const char *article)
Post article.
Definition: nntp.c:3591
nntp_get_capa_over
int nntp_get_capa_over(int handle)
Get overview capability of NNTP server.
Definition: nntp.c:3279
nntp_get_motd
int nntp_get_motd(int handle, char **data, size_t *len)
Get message of the day.
Definition: nntp.c:2448
tls_free
void tls_free(void *)
Free an object allocated by TLS module.
Definition: tls.c:2893
CONF_DIST_SUGG
Definition: conf.h:71
NNTP_LINELENGTHMAX
#define NNTP_LINELENGTHMAX
Maximum line length (for command/response, not payload)
Definition: nntp.c:90
tls_send
posix_ssize_t tls_send(void *, const void *, size_t)
Send data.
Definition: tls.c:2754
tls_close
int tls_close(void **)
Terminate TLS encryption layer on top of network connection.
Definition: tls.c:2311
nntp_grouplabel::label
char * label
Definition: nntp.h:44
cmpr_recv
posix_ssize_t cmpr_recv(void *stream, void *buf, size_t len, int peek)
Receive data.
Definition: compression.c:443
core_anum_t
#define core_anum_t
Article number data type (value zero is always reserved)
Definition: core.h:24
nntp_get_article_header
int nntp_get_article_header(int handle, const nntp_anum_t *id, char **header, size_t *len)
Get article header.
Definition: nntp.c:3494
nntp_groupdesc::name
char * name
Definition: nntp.h:33
cmpr_terminate
int cmpr_terminate(void *stream)
Flush TX direction and terminate data stream.
Definition: compression.c:410
nntp_grouplabel::name
char * name
Definition: nntp.h:43
tls_recv
posix_ssize_t tls_recv(void *, void *, size_t, int)
Receive data.
Definition: tls.c:2791
tls_cert_get_string
int tls_cert_get_string(void *, const char **)
Print certificate.
Definition: tls.c:2706
enc_ascii_check
int enc_ascii_check(const char *s)
Verify ASCII encoding.
Definition: encoding.c:4944
nntp_get_capa_list_motd
int nntp_get_capa_list_motd(int handle)
Get message of the day capability of NNTP server.
Definition: nntp.c:2416
tls_get_ciphersuite
int tls_get_ciphersuite(void **, const char **, const char **, const char **)
Get protocol and cipher suite name that was negotiated for connection.
Definition: tls.c:2360
config
struct conf config[CONF_NUM]
Global configuration.
Definition: conf.c:63
enc_convert_ascii_to_anum
int enc_convert_ascii_to_anum(core_anum_t *result, const char *wm, int len)
Convert number from ASCII to numerical format.
Definition: encoding.c:4604
tls_open
int tls_open(int, void **, int, const char *)
Establish TLS encryption layer on top of open network connection.
Definition: tls.c:2077
NNTP_ARGLENGTHMAX
#define NNTP_ARGLENGTHMAX
NNTP V2 maximum command argument length (according to RFC 3977)
Definition: nntp.c:93
cmpr_flush
int cmpr_flush(void *stream)
Flush TX direction.
Definition: compression.c:384
nntp_grouplabel
NNTP group label (description string)
Definition: nntp.h:41
log_add
void log_add(FILE *, const char *)
Add data to end of logfile.
Definition: log.c:175
nntp_groupdesc
NNTP group descriptor.
Definition: nntp.h:31
nntp_get_capa_list_subscriptions
int nntp_get_capa_list_subscriptions(int handle)
Get message of the day capability of NNTP server.
Definition: nntp.c:2550
NNTP_GROUP_FLAG_ASCII
#define NNTP_GROUP_FLAG_ASCII
Definition: nntp.h:63
nntp_group_descriptor_constructor
struct nntp_groupdesc * nntp_group_descriptor_constructor(const char *name)
Allocate and initialize a descriptor for group.
Definition: nntp.c:2638
enc_ascii_check_printable
int enc_ascii_check_printable(const char *s)
Check for printable ASCII characters.
Definition: encoding.c:5022
nntp_groupdesc::lwm
nntp_anum_t lwm
Definition: nntp.h:35
log_close_logfile
void log_close_logfile(FILE **)
Close logfile.
Definition: log.c:158
inet_set_tx_timeout
int inet_set_tx_timeout(int, unsigned int)
Try to set TX timeout for socket.
Definition: inet.c:477
inet_close
void inet_close(int *)
Close connection and destroy socket.
Definition: inet.c:524
nntp_open
int nntp_open(int *handle, const char *servername, const char *service, const char *logfile, int enc, int auth,...)
Open connection to NNTP server.
Definition: nntp.c:2070
nntp_get_group_labels
int nntp_get_group_labels(int handle, size_t *groupcount, struct nntp_grouplabel **p)
Get additional group information.
Definition: nntp.c:2937
enc_ascii_check_digit
int enc_ascii_check_digit(const char *s)
Check for ASCII digit characters.
Definition: encoding.c:4995
CONF_COMPRESSION
Definition: conf.h:72
nntp_get_article
int nntp_get_article(int handle, const nntp_anum_t *id, char **article, size_t *len)
Get complete article.
Definition: nntp.c:3444
enc_uc_check_utf8
int enc_uc_check_utf8(const char *s)
Verify UTF-8 encoding.
Definition: encoding.c:5162
enc_convert_anum_to_ascii
int enc_convert_anum_to_ascii(char result[17], size_t *len, core_anum_t wm)
Convert article number from numerical format to ASCII.
Definition: encoding.c:4558
cmpr_stream_destructor
void cmpr_stream_destructor(void *stream)
Destroy compressed data stream object allocated by compression module.
Definition: compression.c:768
tls_cert_verify
int tls_cert_verify(void **, void **, const char *, int)
Check whether server has presented a certificate and verify it.
Definition: tls.c:2468
PRINT_ERROR
#define PRINT_ERROR(s)
Prepend module prefix and print error message.
Definition: main.h:19
data
struct core_data data
Global data object (shared by all threads)
Definition: core.c:242
cmpr_stream_constructor
struct cmpr_stream * cmpr_stream_constructor(unsigned int alg, int sd, ssize_t(*tx_send)(int, const void *, size_t, int), ssize_t(*rx_recv)(int, void *, size_t, int))
Compressed data stream object constructor.
Definition: compression.c:615
tls_sni
const char * tls_sni(const char *)
Check whether server name used for connection is not an IP address.
Definition: tls.c:2004
nntp_set_group
int nntp_set_group(int handle, const char *name, struct nntp_groupdesc **gd)
Set current group.
Definition: nntp.c:3124
nntp_get_overview
int nntp_get_overview(int handle, nntp_anum_t first, nntp_anum_t last, char **data, size_t *len)
Get overview for article range.
Definition: nntp.c:3344
CMPR_ALG_DEFLATE
#define CMPR_ALG_DEFLATE
DEFLATE according to RFC 1951.
Definition: compression.h:23
nntp_get_grouplist
int nntp_get_grouplist(int handle, size_t *groupcount, struct nntp_groupdesc **p)
Get group list.
Definition: nntp.c:2683
NNTP_ANUM_T_MAX
#define NNTP_ANUM_T_MAX
Maximum value this implementation supports for nntp_anum_t.
Definition: nntp.h:52
nntp_get_article_body
int nntp_get_article_body(int handle, const nntp_anum_t *id, char **body, size_t *len)
Get article body.
Definition: nntp.c:3544
log_open_logfile
int log_open_logfile(FILE **, const char *)
Open logfile.
Definition: log.c:114
NNTP_GROUP_FLAG_PA
#define NNTP_GROUP_FLAG_PA
Definition: nntp.h:65
nntp_groupdesc::hwm
nntp_anum_t hwm
Definition: nntp.h:36
NNTP_HANDLEMAX
#define NNTP_HANDLEMAX
Maximum number of simultaneous NNTP connections.
Definition: nntp.c:80
nntp_close
void nntp_close(int *handle, unsigned int flags)
Disconnect from NNTP server.
Definition: nntp.c:2348
cmpr_send
posix_ssize_t cmpr_send(void *stream, const void *buf, size_t len)
Send data.
Definition: compression.c:362
nntp_get_distrib_pats
int nntp_get_distrib_pats(int handle, const char **data, size_t *len)
Get distribution patterns.
Definition: nntp.c:2511
NNTP_CLOSE_NOQUIT
#define NNTP_CLOSE_NOQUIT
Definition: nntp.h:57
nntp_get_over_newsgroups_index
int nntp_get_over_newsgroups_index(int handle, size_t *index)
Get index of Newsgroups header field in overview.
Definition: nntp.c:3310
CONF_NO_OVER
Definition: conf.h:70
nntp_anum_t
unsigned long int nntp_anum_t
Article number.
Definition: nntp.h:28
inet_set_rx_timeout
int inet_set_rx_timeout(int, unsigned int)
Try to set RX timeout for socket.
Definition: inet.c:419
nntp_get_article_by_mid
int nntp_get_article_by_mid(int handle, const char *mid, char **article, size_t *len)
Get complete article via Message-ID.
Definition: nntp.c:3394
nntp_get_subscriptions
int nntp_get_subscriptions(int handle, char **data, size_t *len)
Get subscription proposals.
Definition: nntp.c:2585
inet_connect
int inet_connect(int *, int *, const char *, const char *)
Establish stream oriented connection.
Definition: inet.c:254

Generated at 2024-04-27 using  doxygen