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

Generated at 2026-01-27 using  doxygen