core.c
Go to the documentation of this file.
1 /* ========================================================================== */
2 /*! \file
3  * \brief Newsreader core (for handling RFC 5536 conformant messages)
4  *
5  * Copyright (c) 2012-2024 by the developers. See the LICENSE file for details.
6  *
7  * All newsreader functions (but nothing generic like file handling) that are
8  * not related to the transport or the user interface (UI) should be implemented
9  * here.
10  *
11  * If nothing else is specified, functions return zero to indicate success
12  * and a negative value to indicate an error.
13  */
14 
15 
16 /* ========================================================================== */
17 /* Include headers */
18 
19 #include "posix.h" /* Include this first because of feature test macros */
20 
21 #include <ctype.h>
22 #include <stdarg.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <time.h>
26 
27 #include "conf.h"
28 #include "config.h"
29 #include "core.h"
30 #include "database.h"
31 #include "digest.h"
32 #include "encoding.h"
33 #include "extutils.h"
34 #include "fileutils.h"
35 #include "filter.h"
36 #include "group.h"
37 #include "hmac.h"
38 #include "log.h"
39 #include "main.h"
40 #include "secure.h"
41 #include "timing.h"
42 #include "ui.h"
43 #include "xdg.h"
44 
45 
46 /* ========================================================================== */
47 /*! \defgroup CORE CORE: Newsreader base functionality
48  *
49  * The core use a separate thread for the transport subsystem calls, otherwise
50  * it would block the UI while waiting for data.
51  *
52  * A nexus binds the core to a transport subsystem (like NNTP or UUCP).
53  *
54  * Current limitations:
55  * - Only one server nexus is supported at a time
56  * - Only NNTP transport is supported
57  * - There is no command queue
58  * - No Unicode support at protocol level (see below)
59  *
60  * At least the following things must be (re)implemented for Unicode support at
61  * protocol level:
62  * - The NNTP transport driver already supports Unicode group names.
63  * This support currently only implements encoding checks. There is no
64  * normalization or equality matching.
65  * - The article header cache database currently is based on the POSIX portable
66  * filename character set. For Unicode group names the database must be
67  * modified or replaced.
68  * - Unicode allows different legal encodings and/or composition variants for
69  * the same string. This makes all protocol entities that are encoded this way
70  * ambiguous by definition. This is not acceptable because we internally use
71  * protocol entities like group names as identifiers. This means that any
72  * incoming identifier must be normalized before internal use to be non-
73  * ambiguous. Read this document for details about normalization:
74  * http://www.unicode.org/reports/tr15/ .
75  * Normalization to NFC can be provided by the ENCODING module.
76  * - For outgoing names there is no normalization defined in NNTP yet.
77  * Most likely this will become the incoming format from that server in the
78  * future. This means we need to store two representations of names (one
79  * internal form and an external form that is server dependent).
80  * - The '~/.newsrc' file is ASCII encoded. This can't be changed without
81  * breaking compatibility with other newsreaders. A separate database for
82  * Unicode group states must be implemented while the ASCII group states
83  * should stay in the old place for backward compatibility.
84  * - RFC 5536 conformant article headers are not allowed to contain Unicode,
85  * MIME encoded words are not allowed in the "Newsgroups" header field too
86  * (see chapter 3.1.4). We need a new parser that is conformant to a successor
87  * of RFC 5536.
88  * - The handling for LIST DISTRIB.PATS in the CORE module must be rewritten
89  * to check for a UTF-8 based locale and convert the data otherwise (for the
90  * wildmat matching via POSIX extended regular expressions).
91  *
92  * \attention
93  * It is required that 'PID_MAX' is not larger than 'LONG_MAX' (must be checked
94  * by build system).
95  */
96 /*! @{ */
97 
98 
99 /* ========================================================================== */
100 /* Data types */
101 
102 enum core_nexus_state
103 {
104  CORE_NEXUS_CLOSED,
105  CORE_NEXUS_ESTABLISHED
106 };
107 
108 enum core_command
109 {
110  CORE_CMD_INVALID,
111  CORE_CMD_GET_MOTD,
112  CORE_CMD_GET_DISTRIB_PATS,
113  CORE_CMD_GET_SUBSCRIPTIONS,
114  CORE_CMD_RESET_GROUPSTATES,
115  CORE_CMD_GET_GROUPLIST,
116  CORE_CMD_GET_GROUPLABELS,
117  CORE_CMD_GET_GROUPINFO,
118  CORE_CMD_SET_GROUP,
119  CORE_CMD_GET_OVERVIEW,
120  CORE_CMD_GET_ARTICLE_BY_MID,
121  CORE_CMD_GET_ARTICLE,
122  CORE_CMD_GET_ARTICLE_HEADER,
123  CORE_CMD_GET_ARTICLE_BODY,
124  CORE_CMD_POST_ARTICLE,
125  CORE_TERMINATE_NEXUS
126 };
127 
128 enum core_header_type
129 {
130  CORE_HT_UNSTRUCT,
131  CORE_HT_STRUCT
132 };
133 
134 enum core_header_id
135 {
136  /* End of header marker */
137  CORE_HDR_EOH,
138  /* IDs for mandatory header fields according to RFC 5536 */
139  CORE_HDR_MSGID, /* "Message-ID" */
140  CORE_HDR_GROUPS, /* "Newsgroups" */
141  CORE_HDR_FROM, /* "From" */
142  CORE_HDR_SUBJECT, /* "Subject" */
143  CORE_HDR_DATE, /* "Date" */
144  /* Alyways keep optional headers behind this entry */
145  CORE_HDR_OPT,
146  /* IDs for optional header fields according to RFC 5536 */
147  CORE_HDR_SUPERS, /* "Supersedes" */
148  CORE_HDR_FUP2, /* "Followup-To" */
149  CORE_HDR_REPLY2, /* "Reply-To" */
150  CORE_HDR_UAGENT, /* "User-Agent" */
151  CORE_HDR_ORG, /* "Organization" */
152  CORE_HDR_REFS, /* "References" */
153  CORE_HDR_DIST, /* "Distribution" */
154  /* IDs for optional header fields according to RFC 2047 */
155  CORE_HDR_MIME, /* "MIME-Version" */
156  CORE_HDR_CT, /* "Content-Type" */
157  CORE_HDR_CTE, /* "Content-Transfer-Encoding" */
158  CORE_HDR_CD, /* "Content-Disposition" */
159  /* Nonstandard header fields */
160  CORE_HDR_X_NEWSR, /* "X-Newsreader" */
161  CORE_HDR_X_MAILER, /* "X-Mailer" */
162  CORE_HDR_X_PAGENT, /* "X-Posting-Agent" */
163  /* Obsolete header fields */
164  CORE_HDR_LINES /* "Lines" */
165 };
166 
167 struct core_headerfield
168 {
169  enum core_header_id id;
170  enum core_header_type type;
171  const char* content;
172 };
173 
174 struct core_nexus
175 {
176  enum core_nexus_state nntp_state;
177  const char* nntp_server;
178  int nntp_handle;
179  const char* nntp_current_group;
180 };
181 
182 struct distrib_pats
183 {
184  const char* wildmat;
185  size_t weight;
186  const char* dist;
187 };
188 
189 
190 /* ========================================================================== */
191 /* Constants */
192 
193 /*! \brief Message prefix for CORE module */
194 #define MAIN_ERR_PREFIX "CORE: "
195 
196 /*! \brief Number of retries for nexus operations
197  *
198  * Should be at least 1, otherwise the core module can't automatically recover
199  * after a nexus loss (e.g. disconnect from server).
200  */
201 #define CORE_NEXUS_RETRIES 1U
202 
203 /*! \brief Sufficient for any RFC 5536 conformant header line
204  *
205  * The real limit defined in RFC 5536 is 998 characters.
206  */
207 #define CORE_HEADER_LINE_LENGTH (size_t) 1024
208 
209 /*! \name Control flags for header parser (for internal use only)
210  *
211  * The flags can be bitwise ORed together.
212  */
213 /*! @{ */
214 #define CORE_CFLAG_COMMENT 0x01U
215 #define CORE_CFLAG_QSTRING 0x02U
216 #define CORE_CFLAG_EWORD 0x04U
217 /*! @} */
218 
219 /*! File containing secret for SHA2-based Cancel-Locks/-Keys
220  *
221  * \note The minimum value for NAME_MAX defined by POSIX is 14 characters.
222  */
223 #define CORE_CL_SECRET_FILE ".cancelsecret"
224 
225 /*! \brief Do not parse for content in "User-Agent" header field
226  *
227  * Set this to nonzero if you want to see the comments in the GUI.
228  *
229  * \attention
230  * The contents of comments can contain quoted-pair and encoded-word tokens.
231  * The header parser will not decode them and the data may not be readable
232  * for humans (e.g. Base64 encoding in encoded-word). Therefore this option
233  * is disabled by default.
234  */
235 #define CORE_UAGENT_RAW 0
236 
237 
238 /* ========================================================================== */
239 /* Variables */
240 
241 /*! \brief Global data object (shared by all threads) */
243 
244 static api_posix_pthread_t pt;
245 static int pt_valid = 0;
246 static api_posix_pthread_t ui_pt;
247 static api_posix_pthread_mutex_t pt_mutex = API_POSIX_PTHREAD_MUTEX_INITIALIZER;
248 static api_posix_pthread_cond_t pt_cond = API_POSIX_PTHREAD_COND_INITIALIZER;
249 static struct core_hierarchy_element* h = NULL;
250 static struct core_nexus* n = NULL;
251 static enum core_command command = CORE_CMD_INVALID;
252 
253 
254 /* ========================================================================== */
255 /* Check whether character is part of quoted-string and/or comment
256  *
257  * \param[in] s String
258  * \param[in] p Pointer to character inside string \e s
259  * \param[in] flags Control flags
260  *
261  * Only the conditions marked with flags are reported.
262  *
263  * \return
264  * - 0 if character is not part of specified tokens
265  * - Positive value if \e p points inside one of specified tokens
266  */
267 
268 static int check_iqscf(const char* s, const char* p, unsigned int flags)
269 {
270  int res = 0;
271  size_t i = 0;
272  int escape = 0;
273  int lwbs = 0; /* Last character was backslash flag */
274  int lweq = 0; /* Last character was equal sign flag*/
275  int lwqm = 0; /* Last character was question mark flag*/
276  int iew = 0; /* Inside encoded-word flag */
277  int iqs = 0; /* Inside quoted-string flag */
278  unsigned int cmt = 0; /* Comment nesting depth */
279 
280  while(s[i])
281  {
282  /* Check for start of encoded-word */
283  if(!iew && lweq && '?' == s[i]) { iew = 1; }
284  /* Check for quoted-pair */
285  if(iqs || cmt)
286  {
287  if(lwbs) { escape = 1; } else { escape = 0; }
288  }
289  if(0x5C == s[i] && !escape) { lwbs = 1; } else { lwbs = 0; }
290  /* Check for start of comment (can be nested) */
291  if(!iew && !iqs && '(' == s[i])
292  {
293  if(!escape)
294  {
295  if(API_POSIX_UINT_MAX == cmt)
296  {
297  /* Comment nesting depth overflow */
298  PRINT_ERROR("Header parser: Too many nested comments");
299  }
300  else { ++cmt; }
301  }
302  }
303  /* Check for start of quoted string */
304  if(!iew && !cmt && '"' == s[i])
305  {
306  if(!escape)
307  {
308  if(!iqs) { iqs = 1; } else { iqs = 2; }
309  }
310  }
311  /* Check for match */
312  if(p == &s[i])
313  {
314  if(CORE_CFLAG_COMMENT & flags && cmt)
315  {
316  res |= (int) CORE_CFLAG_COMMENT;
317  }
318  if(CORE_CFLAG_QSTRING & flags && iqs)
319  {
320  res |= (int) CORE_CFLAG_QSTRING;
321  }
322  if(CORE_CFLAG_EWORD & flags && iew)
323  {
324  res |= (int) CORE_CFLAG_EWORD;
325  }
326  break;
327  }
328 #if 0
329  /* For debugging */
330  if(!i)
331  {
332  printf("I");
333  if(CORE_CFLAG_EWORD & flags) { printf("W"); }
334  else { printf(" "); }
335  if(CORE_CFLAG_QSTRING & flags) { printf("S"); }
336  else { printf(" "); }
337  if(CORE_CFLAG_COMMENT & flags) { printf("C"); }
338  else { printf(" "); }
339  printf(": %s\n", s);
340  printf(" ");
341  }
342 # if 0
343  /* Mark quoted-pair escaping */
344  if(escape) { printf("E"); } else
345 # endif
346  if(iqs) { printf("S"); }
347  else if(cmt)
348  {
349  if(9 >= cmt) { printf("%u", cmt); } else { printf("0"); }
350  }
351  else { printf(" "); }
352 #endif
353  /* Check for end of comment */
354  if(!iew && !iqs && ')' == s[i])
355  {
356  if(!escape)
357  {
358  if(cmt) { --cmt; }
359  else
360  {
361  /* Opening parenthesis missing */
362  PRINT_ERROR("Header parser: Syntax error in comment");
363  }
364  }
365  }
366  /* Check for end of quoted-string */
367  if(!iew && !cmt && '"' == s[i])
368  {
369  if(!escape && 1 < iqs) { iqs = !iqs; }
370  }
371  /* Check for end of encoded-word */
372  if(iew && lwqm && '=' == s[i]) { iew = 0; }
373  if('=' == s[i]) { lweq = 1; } else { lweq = 0; }
374  if('?' == s[i]) { lwqm = 1; } else { lwqm = 0; }
375  /* Next character */
376  ++i;
377  }
378 #if 0
379  /* For debugging */
380  printf("^ (res: %d)\n", res);
381 #endif
382 
383  return(res);
384 }
385 
386 
387 /* ========================================================================== */
388 /* Check whether character is part of comment
389  *
390  * \param[in] s String
391  * \param[in] p Pointer to character inside string \e s
392  *
393  * \return
394  * - 0 if character pointed to by \e p is not part of comment
395  * - Negative value if \e p points inside comment or is not found
396  */
397 
398 static int check_ic(const char* s, const char* p)
399 {
400  return(check_iqscf(s, p, CORE_CFLAG_COMMENT));
401 }
402 
403 
404 /* ========================================================================== */
405 /* Check whether character is part of quoted-string or comment
406  *
407  * \param[in] s String
408  * \param[in] p Pointer to character inside string \e s
409  *
410  * \return
411  * - 0 if character pointed to by \e p is syntactically used
412  * - Negative value if \e p points inside quoted-string, comment or is not found
413  */
414 
415 static int check_iqsc(const char* s, const char* p)
416 {
417  return(check_iqscf(s, p, CORE_CFLAG_QSTRING | CORE_CFLAG_COMMENT));
418 }
419 
420 
421 /* ========================================================================== */
422 /* Convert "From" header field from RFC 850 to RFC 5536 format
423  *
424  * According to RFC 850 the following rule is applied:
425  * - A name in parenthesis that follows the address is not a comment. This is no
426  * longer allowed by RFC 5536 and today defined as regular comment
427  * => We accept the RFC 850 format for backward compatibility and convert it
428  * to RFC 5536 format so that the header parser can process it.
429  *
430  * This function converts:
431  * foo@bar.com (Full name)
432  * to:
433  * Full name <foo@bar.com>
434  *
435  * \param[in] from Unfolded body of header field "From"
436  *
437  * \note
438  * An SP between address and comment is required in the input data.
439  *
440  * \attention
441  * Only the first mailbox of a list is converted, the others are stripped.
442  *
443  * \attention
444  * On success, the old memory block pointed to by \e from is 'free()'d by this
445  * function!
446  *
447  * This function never fail.
448  *
449  * \return
450  * - \e from or converted string
451  */
452 
453 static char* convert_from_rfc850_to_rfc5536(char* from)
454 {
455  char* res = from;
456  size_t len = strlen(from);
457  char* name;
458  char* cp;
459  char* comma = NULL;
460  char* addr;
461  size_t i;
462  char* p;
463  char* tmp = NULL;
464  int invalid;
465 
466  /* Check whether single comment is present and no angle-addr */
467  name = strchr(from, (int) '(');
468  cp = strchr(from, (int) ')');
469  if(cp) { comma = strchr(&cp[1], (int) ','); }
470  if(NULL != name && cp > name)
471  {
472  /* Ignore comments or angle-addr of other mailboxes */
473  if( (!comma && NULL == strchr(&name[1], (int) '(')
474  && NULL == strchr(from, (int) '<'))
475  ||
476  (comma && (NULL == strchr(&name[1], (int) '(')
477  || strchr(&name[1], (int) '(') > comma)
478  && (NULL == strchr(from, (int) '<')
479  || strchr(from, (int) '<') > comma)) )
480  {
481  /* Allocate temporary buffer (1 additional byte for NUL termination) */
482  tmp = (char*) api_posix_malloc(len * (size_t) 2 + (size_t) 1);
483  if(NULL != tmp)
484  {
485  i = (size_t) (name - from);
486  if(i)
487  {
488  memcpy(tmp, from, len); tmp[len] = 0;
489  if(comma) { tmp[(size_t) (comma - from)] = 0; }
490  /* Extract address */
491  if(' ' == tmp[--i])
492  {
493  tmp[i] = 0;
494  addr = tmp;
495  /* Check address */
496  if(NULL != strchr(addr, (int) '@'))
497  {
498  /* Looks good => Extract name */
499  i += (size_t) 2;
500  name = &tmp[i];
501  p = strchr(name, (int) ')');
502  if(NULL != p)
503  {
504  p[0] = 0;
505  /* Check name */
506  invalid = enc_ascii_check_printable(name);
507  if(invalid)
508  {
509  PRINT_ERROR("Header parser: Control characters"
510  " in RFC 850 full name not supported");
511  }
512  else
513  {
514  if(NULL != strpbrk(name, "()<>,:;"))
515  {
516  PRINT_ERROR("Header parser: Invalid RFC 850"
517  " full name in parenthesis");
518  invalid = 1;
519  }
520  if(!invalid)
521  {
522  /*
523  * Create quoted-pair encoding for '"'characters
524  * that can't be represented literally in a
525  * quoted-string (RFC 850 allow '"' characters in
526  * full name)
527  * Note: We have always allocated enough memory
528  * for the temporary buffer after name!
529  */
530  if(NULL != strchr(name, (int) '"'))
531  {
532  i = 0;
533  while(name[i])
534  {
535  if('"' == name[i])
536  {
537  /* Verify whether already quoted-pair */
538  if( !(i && 0x5C
539  == (int) name[i - (size_t) 1]) )
540  {
541  len = strlen(&name[i]);
542  memmove((void*) (&name[i] + 1),
543  (void*) &name[i], ++len);
544  name[i++] = 0x5C;
545  }
546  }
547  ++i;
548  }
549  }
550  /* Check for single trailing backslash */
551  p = strrchr(name, 0x5C);
552  if(NULL != p && !p[1])
553  {
554  if( (p != name && 0x5C != (int) *(p - 1))
555  || p == name )
556  {
557  PRINT_ERROR("Header parser: Invalid"
558  " quoted-pair in RFC 850"
559  " full name accepted/ignored");
560  *p = 0;
561  }
562  }
563  /* Success => Allocate new memory block */
564  len = 1; /* NUL termination */
565  len += strlen(name);
566  len += strlen(addr);
567  len += (size_t) 2; /* Double quotes */
568  len += (size_t) 1; /* SP separator */
569  len += (size_t) 2; /* Angle brackets */
570  res = (char*) api_posix_malloc(len);
571  if (NULL == res) { res = from; }
572  else
573  {
574  if(!name[0])
575  {
576  /* Omit the separator if name is empty */
577  api_posix_snprintf(res, len, "<%s>", addr);
578  }
579  else
580  {
581  /* Represent the name as quoted string */
582  api_posix_snprintf(res, len, "\"%s\" <%s>",
583  name, addr);
584  }
585  if(comma)
586  {
587  PRINT_ERROR("Header parser: Only"
588  " first mailbox of mailbox-list"
589  " processed");
590  }
591 #if 0
592  /* For debugging */
593  printf("RFC 850 : %s\n", from);
594  printf("RFC 5536: %s\n", res);
595 #endif
596  api_posix_free((void*) from);
597  }
598  }
599  }
600  }
601  }
602  }
603  }
604  }
605  }
606  }
607  api_posix_free((void*) tmp);
608 
609  return(res);
610 }
611 
612 
613 /* ========================================================================== */
614 /* Article header parser */
615 /*
616  * Parses the header 'h' and create an array 'e' that contains structures with
617  * the ID, the type and the extracted bodies of header fields.
618  * For structured header fields, comments are removed and runs of white space
619  * are replaced by a single space.
620  * If the header contains a field multiple times, all of them will be added to
621  * the result array with the same ID.
622  *
623  * Note: What we call a "header field" is called a "header line" by RFC 3977.
624  *
625  * On success, the caller is responsible to free the memory allocated for the
626  * array 'e' and all of the body content where its elements point to.
627  */
628 /*! \todo Better article header parser.
629  * The quick&dirty top down header parser try to behave RFC 5536 conformant.
630  * Because the syntax is very complicated, some special cases that are seldom
631  * used in real world are not handled correctly.
632  * To improve this, we should consider to use a state machine generated by yacc
633  * for the official grammar as parser or at least a lexical analyzer generated
634  * by lex for the official tokens to split them correctly.
635  */
636 /*
637  * According to RFC 5322, the header fields are accepted in arbitrary order.
638  *
639  * According to RFC 822 the following rules are applied:
640  * - All header field names must be treated case-insensitive => We do so.
641  *
642  * According to RFC 5536 the following rules are applied:
643  * - At least 1 space must follow the colon at the end of the header field name
644  * It is allowed to accept header fields without spaces => We do so.
645  * - Header fields with empty body are not allowed
646  * => We ignore them.
647  * - Header fields longer than 998 characters must be folded
648  * It is allowed to accept them unfolded anyhow
649  * => We do so up to CORE_HEADER_LINE_LENGTH.
650  * - Header fields must contain only printable ASCII characters
651  * This is mandatory => All header fields that use anything else are ignored.
652  * - Some header fields are not allowed to contain comments:
653  * Control, Distribution, Followup-To, Lines, Newsgroups, Path, Supersedes,
654  * Xref, Message-ID, Lines
655  * => If there are comments in such a header field we use, the header field is
656  * accepted and the comments stay in place (are treated as part of the body).
657  * - The header field "Lines" is marked as obsolete and it is recommended to
658  * ignore it
659  * => We use it nevertheless because otherwise it is not possible to process
660  * the number of lines information from the overview data
661  */
662 
663 static int header_parser(struct core_headerfield** e, const char* h)
664 {
665  const char* hfields[] =
666  {
667  "MESSAGE-ID", "NEWSGROUPS", "FROM",
668  "SUBJECT", "DATE", "SUPERSEDES",
669  "FOLLOWUP-TO", "REPLY-TO", "USER-AGENT",
670  "ORGANIZATION", "REFERENCES", "DISTRIBUTION",
671  "MIME-VERSION", "CONTENT-TYPE", "CONTENT-TRANSFER-ENCODING",
672  "CONTENT-DISPOSITION",
673  "X-NEWSREADER", "X-MAILER", "X-POSTING-AGENT",
674  "LINES", NULL
675  };
676  /* The order of IDs must match the order of the strings above */
677  enum core_header_id hfieldids[] =
678  {
679  CORE_HDR_MSGID, CORE_HDR_GROUPS, CORE_HDR_FROM,
680  CORE_HDR_SUBJECT, CORE_HDR_DATE, CORE_HDR_SUPERS,
681  CORE_HDR_FUP2, CORE_HDR_REPLY2, CORE_HDR_UAGENT,
682  CORE_HDR_ORG, CORE_HDR_REFS, CORE_HDR_DIST,
683  CORE_HDR_MIME, CORE_HDR_CT, CORE_HDR_CTE,
684  CORE_HDR_CD,
685  CORE_HDR_X_NEWSR, CORE_HDR_X_MAILER, CORE_HDR_X_PAGENT,
686  CORE_HDR_LINES, CORE_HDR_EOH
687  };
688  enum core_header_type hfieldtypes[] =
689  {
690  CORE_HT_STRUCT, CORE_HT_STRUCT, CORE_HT_STRUCT,
691  CORE_HT_UNSTRUCT, CORE_HT_STRUCT, CORE_HT_STRUCT,
692  CORE_HT_STRUCT, CORE_HT_STRUCT, CORE_HT_STRUCT,
693  CORE_HT_UNSTRUCT, CORE_HT_STRUCT, CORE_HT_STRUCT,
694  CORE_HT_STRUCT, CORE_HT_STRUCT, CORE_HT_STRUCT,
695  CORE_HT_STRUCT,
696  CORE_HT_STRUCT, CORE_HT_STRUCT, CORE_HT_STRUCT,
697  CORE_HT_STRUCT, CORE_HT_UNSTRUCT
698  };
699  int res = 0;
700  size_t asize = 16; /* Initial size of result array */
701  size_t ai = 0;
702  size_t i = 0;
703  size_t ii;
704  const char* target;
705  char* buf = NULL;
706  char* p;
707  char* q;
708  size_t buflen = CORE_HEADER_LINE_LENGTH;
709  size_t len;
710  int used; /* Flag indicating header field is known and used */
711  int qstring;
712  int skip;
713  int resync;
714  struct core_headerfield hf;
715  char* gstart;
716  char* gend;
717  char* comment;
718  struct core_headerfield* tmp;
719  int inside_qs;
720 
721  /* Allocate memory for result array */
722  *e = (struct core_headerfield*)
723  api_posix_malloc(asize * sizeof(struct core_headerfield));
724  if (NULL == *e) { res = -1; }
725 
726  /* Allocate memory for header line */
727  if(!res)
728  {
729  buf = (char*) api_posix_malloc(buflen);
730  if (NULL == buf) { res = -1; }
731  }
732 
733  /* Parser */
734  while(!res && h[i])
735  {
736  /* Extract header field name and convert it to upper case */
737  resync = 0;
738  target = strchr(&h[i], (int) ':');
739  if(target <= &h[i]) { resync = 1; } /* NULL is lower than any pointer */
740  else
741  {
742  len = (size_t) (target - &h[i]);
743  if(buflen <= len) { resync = 1; } /* We need one additional byte */
744  else
745  {
746  for(ii = 0; ii < len; ++ii)
747  {
748  buf[ii] = (char) toupper((int) h[i + ii]);
749  }
750  buf[len] = 0;
751  i += len;
752  }
753  }
754 
755  /* Check whether we use this header field */
756  if(!resync)
757  {
758  used = 0;
759  ii = 0;
760  while(NULL != hfields[ii])
761  {
762  if(!strcmp(hfields[ii], buf)) { used = 1; break; }
763  ++ii;
764  }
765  if(!used)
766  {
767  /* We don't use this header field => Ignore */
768  resync = 1;
769  }
770  else
771  {
772  /* Used header field found => Store ID and type */
773  hf.id = hfieldids[ii];
774  hf.type = hfieldtypes[ii];
775  }
776  }
777 
778  /* Extract header field body if required */
779  if(!resync)
780  {
781  /* Skip potential leading SPs */
782  if(CORE_HT_STRUCT == hf.type)
783  {
784  /* Remove all spaces */
785  while(' ' == h[++i]);
786  }
787  else
788  {
789  /* Remove only first space (even if multiple are present) */
790  if(' ' == h[++i]) { ++i; };
791  }
792  /* Process field body */
793  buf[0] = 0;
794  ii = 0;
795  do
796  {
797  /* Search for 0x0D = CR (end of body or folding point) */
798  target = strchr(&h[i], 0x0D);
799  if(target <= &h[i]) { resync = 1; break; }
800  else
801  {
802  /* Check whether buffer size must be increased */
803  len = (size_t) (target - &h[i]);
804  while(buflen <= ii + len) /* We need one additional byte */
805  {
806  p = (char*) api_posix_realloc(buf, buflen *= (size_t) 2);
807  if(NULL == p) { res = -1; break; } else { buf = p; }
808  }
809  if(-1 == res) { break; }
810  /* Copy next chunk of body to buffer */
811  memcpy((void*) &buf[ii], (void*) &h[i], len);
812  buf[ii += len] = 0;
813  i += len;
814  }
815  /* Verify correct line termination */
816  if((const char) 0x0A != target[1])
817  {
818  PRINT_ERROR("Header parser: Invalid CR in field body");
819  resync = 1;
820  break;
821  }
822  /* Check for folded header field */
823  if((const char) 0x09 == target[2] || (const char) 0x20 == target[2])
824  {
825  /* Yes => Unfold next line */
826  i += 2;
827  if(CORE_HT_STRUCT == hf.type)
828  {
829  /*
830  * According to RFC 5322, runs of FWS, comment or CFWS are
831  * semantically interpreted as single SP.
832  * Note: This rule only applies to structured header fields!
833  */
834  buf[ii++] = 0x20;
835  while((const char) 0x09 == h[i] || (const char) 0x20 == h[i])
836  {
837  ++i;
838  }
839  }
840  }
841  else { break; }
842  }
843  while(!resync);
844  if(res) { break; }
845  }
846 
847  /* Verify header field body, remove comments and process quoted strings */
848  if(!resync)
849  {
850  /* Check for empty header field body */
851  if(!buf[0]) { resync = 1; }
852  else
853  {
854  if(CORE_HDR_DIST == hf.id)
855  {
856  /* Check and reformat distribution header field */
858  }
859  else
860  {
861  /* Check whether header field body is printable ASCII */
862  if(0 > enc_ascii_check_printable(buf))
863  {
864  /* No => Repair */
865  PRINT_ERROR("Header parser: "
866  "Invalid characters replaced");
868  }
869  }
870  /* Check for structured header field */
871  if(CORE_HT_STRUCT == hf.type)
872  {
873  /* Remove comments */
874  switch(hf.id)
875  {
876 #if CORE_UAGENT_RAW
877  case CORE_HDR_UAGENT:
878  {
879  break;
880  }
881 #endif /* CORE_UAGENT_RAW */
882  case CORE_HDR_MSGID:
883  case CORE_HDR_GROUPS:
884  case CORE_HDR_FUP2:
885  case CORE_HDR_SUPERS:
886  case CORE_HDR_DIST:
887  case CORE_HDR_LINES:
888  {
889  /* No comments allowed in these header fields */
890  break;
891  }
892  case CORE_HDR_REPLY2:
893  case CORE_HDR_FROM:
894  {
895  /*
896  * RFC 850 allows full name in comment as exception!
897  * => Convert RFC 850 address to RFC 5536 format or the
898  * will be lost otherwise.
899  */
900  buf = convert_from_rfc850_to_rfc5536(buf);
901  /* No break here is intended! */
902  }
903  /* FALLTHROUGH */
904  default:
905  {
906  ii = 0;
907  comment = NULL;
908  while(buf[ii])
909  {
910  /* Check for start of comment */
911  if('(' == buf[ii] && NULL == comment)
912  {
913  if(check_ic(buf, &buf[ii])) { comment = &buf[ii]; }
914  }
915  /* Check for end of comment */
916  else if(NULL != comment && ')' == buf[ii])
917  {
918  if(!check_ic(buf, &buf[ii + (size_t) 1]))
919  {
920  /* Skip comment */
921  len = strlen(&buf[++ii]);
922  memmove((void*) comment, (void*) &buf[ii], ++len);
923  ii = (size_t) (comment - buf);
924  comment = NULL;
925  continue;
926  }
927  }
928  ++ii;
929  }
930  }
931  }
932  /* Treat runs of WSP semantically as single space */
933  ii = 0;
934  while(buf[ii])
935  {
936  /* Replace HT with SP */
937  if((char) 0x09 == buf[ii]) { buf[ii] = ' '; }
938  if(ii && ' ' == buf[ii])
939  {
940  if(' ' == buf[ii - (size_t) 1])
941  {
942  p = &buf[ii++];
943  if( !( (size_t) 2 <= ii
944  && 0x5C == (int) buf[ii - (size_t) 2]
945  && check_iqsc(buf, p) ) )
946  {
947  len = strlen(&buf[ii]);
948  memmove((void*) p, (void*) &buf[ii], ++len);
949  --ii;
950  }
951  }
952  else { ++ii; }
953  }
954  else { ++ii; }
955  }
956  /* Remove potential leading SP */
957  if(' ' == *buf)
958  {
959  len = strlen(&buf[1]);
960  memmove((void*) buf, (void*) &buf[1], ++len);
961  }
962  /* Special handling for "Content-Type" field */
963  if(CORE_HDR_CT == hf.id)
964  {
965  inside_qs = 0;
966  ii = 0;
967  while(buf[++ii])
968  {
969  /* Remove whitespace before semicolon, slash, equal sign */
970  if('"' == buf[ii])
971  {
972  /*
973  * Parameter values are allowed to contain whitespace and
974  * '=' inside quoted-string => Don't touch this whitespace.
975  */
976  if(!inside_qs) { inside_qs = 1; }
977  else
978  {
979  /* Check for quoted-pair */
980  if((char) 0x5C != buf[ii - 1]) { inside_qs = 0; }
981  }
982  }
983  if(!inside_qs &&
984  (';' == buf[ii] || '=' == buf[ii] || '/' == buf[ii]))
985  {
986  if(' ' == buf[ii - 1])
987  {
988  len = strlen(&buf[ii]);
989  memmove((void*) &buf[ii - 1], (void*) &buf[ii],
990  ++len);
991  --ii;
992  }
993  }
994  /* Remove whitespace after slash and equal sign */
995  if(!inside_qs && ('=' == buf[ii] || '/' == buf[ii]))
996  {
997  if(' ' == buf[ii + 1])
998  {
999  len = strlen(&buf[ii + 2]);
1000  memmove((void*) &buf[ii + 1], (void*) &buf[ii + 2],
1001  ++len);
1002  }
1003  }
1004  }
1005  }
1006  /* Special handling for "Content-Transfer-Encoding" field */
1007  else if(CORE_HDR_CTE == hf.id)
1008  {
1009  /* Strip all content after first occurence of whitespace */
1010  ii = 0;
1011  while(buf[ii++])
1012  {
1013  if(' ' == buf[ii])
1014  {
1015  PRINT_ERROR("Header parser: Garbage at end of"
1016  " Content-Transfer-Encoding field ignored");
1017  buf[ii] = 0;
1018  }
1019  }
1020  }
1021  /* Special handling for "From" and "Reply-To" fields */
1022  else if(CORE_HDR_FROM == hf.id || CORE_HDR_REPLY2 == hf.id)
1023  {
1024  /* address-list is not supported, extract first group */
1025  if(CORE_HDR_REPLY2 == hf.id)
1026  {
1027  q = buf;
1028  do { gstart = strchr(q, (int) ':'); q = gstart + 1; }
1029  while(NULL != gstart && check_iqsc(buf, gstart));
1030  if(NULL != gstart)
1031  {
1032  q = gstart;
1033  do { gend = strchr(q, (int) ';'); q = gend + 1; }
1034  while(NULL != gend && check_iqsc(buf, gend));
1035  if(gstart < gend)
1036  {
1037  /* Check whether group is first address */
1038  q = buf;
1039  do { p = strchr(q, (int) ','); q = p + 1; }
1040  while(NULL != p && check_iqsc(buf, p));
1041  if(NULL == p || p > gstart)
1042  {
1043  /* Yes => Extract group-list */
1044  PRINT_ERROR("Header parser: Only first"
1045  " address of address-list processed");
1046  *gend = 0;
1047  while(++gstart < gend)
1048  {
1049  /* Skip leading whitespace */
1050  if(' ' != *gstart && 0x09 != (int) *gstart)
1051  {
1052  break;
1053  }
1054  }
1055  len = (size_t) (gend - gstart);
1056  memmove((void*) buf, (void*) gstart, ++len);
1057  }
1058  }
1059  }
1060  }
1061  /* mailbox-list is not supported, extract first mailbox */
1062  q = buf;
1063  do { p = strchr(q, (int) ','); q = p + 1; }
1064  while(NULL != p && check_iqsc(buf, p));
1065  if(NULL != p)
1066  {
1067  PRINT_ERROR("Header parser: Only first mailbox of"
1068  " mailbox-list processed");
1069  *p = 0;
1070  }
1071  /* Remove whitespace from addr-spec */
1072  p = strrchr(buf, (int) '<'); if(NULL == p) { p = buf; }
1073  ii = 0;
1074  while(buf[ii])
1075  {
1076  if(&buf[ii] < p) { ++ii; continue; }
1077  if(ii && ' ' == buf[ii])
1078  {
1079  /* Check for "after < or @" and "before > or @" */
1080  if( '<' == buf[ii - (size_t) 1]
1081  || '@' == buf[ii - (size_t) 1]
1082  || '>' == buf[ii + (size_t) 1]
1083  || '@' == buf[ii + (size_t) 1] )
1084  {
1085  p = &buf[ii];
1086  len = strlen(&buf[++ii]);
1087  memmove((void*) p, (void*) &buf[ii], ++len);
1088  }
1089  else { ++ii; }
1090  }
1091  else { ++ii; }
1092  }
1093  }
1094 #if !CORE_UAGENT_RAW
1095  /* Special handling for "User-Agent" field */
1096  else if(CORE_HDR_UAGENT == hf.id)
1097  {
1098  /* Remove whitespace between product and product-version */
1099  ii = 0;
1100  while(buf[ii])
1101  {
1102  if(ii && ' ' == buf[ii])
1103  {
1104  /* Check for "after or before /" */
1105  if( '/' == buf[ii - (size_t) 1]
1106  || '/' == buf[ii + (size_t) 1] )
1107  {
1108  p = &buf[ii];
1109  len = strlen(&buf[++ii]);
1110  memmove((void*) p, (void*) &buf[ii], ++len);
1111  }
1112  else { ++ii; }
1113  }
1114  else { ++ii; }
1115  }
1116  }
1117 #endif /* CORE_UAGENT_RAW */
1118 #if CORE_UAGENT_RAW
1119  if(CORE_HDR_UAGENT != hf.id)
1120  {
1121 #endif /* CORE_UAGENT_RAW */
1122  /* Process quoted strings (potential quoted pairs inside) */
1123  ii = 0;
1124  qstring = 0;
1125  skip = 0;
1126  while(buf[ii])
1127  {
1128  if('"' == buf[ii]) { qstring = !qstring; skip = 1; }
1129  if(qstring && (char) 0x5C == buf[ii]) { skip = 2; }
1130  if(skip)
1131  {
1132  p = &buf[ii];
1133  len = strlen(&buf[++ii]);
1134  memmove((void*) p, (void*) &buf[ii], ++len);
1135  /* Check current position again if not quoted pair */
1136  if(1 == skip) { --ii; }
1137  skip = 0;
1138  }
1139  else { ++ii; }
1140  }
1141 #if CORE_UAGENT_RAW
1142  }
1143 #endif /* CORE_UAGENT_RAW */
1144  }
1145  /* Processing of header field completed => Store body */
1146  hf.content = buf;
1147  }
1148  }
1149 
1150  /* Add header field to result array */
1151  if(!resync)
1152  {
1153  /* Allocate more memory for result buffer if required */
1154  if(ai >= asize - (size_t) 2)
1155  {
1156  tmp = (struct core_headerfield*)
1157  api_posix_realloc(*e, (asize *= (size_t) 2)
1158  * sizeof(struct core_headerfield));
1159  if (NULL == tmp) { res = -1; break; } else { *e = tmp; }
1160  }
1161  (*e)[ai].id = hf.id;
1162  (*e)[ai++].content = hf.content;
1163  /* Allocate new buffer for next header field */
1164  buflen = CORE_HEADER_LINE_LENGTH;
1165  buf = (char*) api_posix_malloc(buflen);
1166  if (NULL == buf) { res = -1; break; }
1167  }
1168 
1169  /* Resync to start of next header field */
1170  do
1171  {
1172  /* Search for 0x0D = CR (end of body or folding point) */
1173  target = strchr(&h[i], 0x0D);
1174  if(NULL == target)
1175  {
1176  PRINT_ERROR("Header parser: Body separator missing");
1177  res = -1;
1178  break;
1179  }
1180  else { i += (size_t) (++target - &h[i]);}
1181  /* Search for 0x0A = LF */
1182  if((const char) 0x0A == h[i]) { ++i; }
1183  /* Check for end of header */
1184  if((const char) 0x0D == h[i])
1185  {
1186  if((const char) 0x0A == h[i + 1])
1187  {
1188  /* End of header detected */
1189  /* printf("EOH detected\n"); */
1190  res = 1;
1191  break;
1192  }
1193  }
1194  }
1195  while(h[i] && (' ' == h[i] || (const char) 0x09 == h[i]));
1196  }
1197  /* Terminate result array */
1198  if(*e) { (*e)[ai].id = CORE_HDR_EOH; }
1199 
1200  /* Clean up */
1201  api_posix_free((void*) buf);
1202  if(0 > res)
1203  {
1204  if(*e)
1205  {
1206  ai = 0;
1207  while(CORE_HDR_EOH != (*e)[ai].id)
1208  {
1209  api_posix_free((void*) (*e)[ai++].content);
1210  }
1211  api_posix_free((void*) *e);
1212  }
1213  }
1214  else { res = 0; }
1215 
1216 #if 0
1217  /* For debugging */
1218  if(!res && *e)
1219  {
1220  printf("\n");
1221  for(i = 0; i < 80; ++i) { printf("-"); } printf("\n");
1222  ai = 0;
1223  while(CORE_HDR_EOH != (*e)[ai].id)
1224  {
1225  i = 0;
1226  do
1227  {
1228  if(hfieldids[i] == (*e)[ai].id) { break; }
1229  }
1230  while(hfieldids[i++]);
1231  printf("%s: %s\n", hfields[i], (*e)[ai].content);
1232  ++ai;
1233  }
1234  for(i = 0; i < 80; ++i) { printf("-"); } printf("\n");
1235  printf("\n");
1236  }
1237 #endif
1238 
1239  return(res);
1240 }
1241 
1242 
1243 /* ========================================================================== */
1244 /* Extract groups from 'Newsgroups' header field */
1245 /*
1246  * The return value is 'NULL' on error or a pointer to an array of strings
1247  * otherwise. Because the parameter 'body' is allowed to have arbitrary size,
1248  * this array has arbitrary size too. The array is terminated with a 'NULL'
1249  * pointer. The caller is responsible to free the memory for the array.
1250  */
1251 
1252 static const char** extract_groups(const char* body)
1253 {
1254  const char** res = NULL;
1255  size_t i = 0;
1256  int p_flag = 0; /* Indicates processing of group is in progress */
1257  size_t ns = 0;
1258  size_t ne;
1259  char* group = NULL;
1260  size_t len;
1261  int err = 0;
1262  size_t asize = 1; /* Current size of group array (in elements) */
1263  size_t bsize = 0; /* Current size of memory block (in octets) */
1264  const char** p;
1265 
1266  /* Note: The header parser has already unfolded the body */
1267  do
1268  {
1269  /* Check for EOB or white space */
1270  if(!body[i] || ',' == body[i] || ' ' == body[i]
1271  || (const char) 0x09 == body[i])
1272  {
1273  ne = i;
1274  if(p_flag)
1275  {
1276  /* Extract group */
1277  if (ne < ns) { continue; }
1278  else { len = ne - ns; }
1279  group = (char*) api_posix_malloc(len + (size_t) 1);
1280  if(NULL == group) { err = 1; break; }
1281  memcpy(group, &body[ns], len);
1282  group[len] = 0;
1283  /* printf("Group: %s\n", group); */
1284  /* Add pointer to array */
1285  if(API_POSIX_SIZE_MAX == asize) { err = 1; break; }
1286  len = (asize + (size_t) 1) * sizeof(const char*);
1287  if(len >= bsize)
1288  {
1289  /* Allocate more memory in exponentially increasing chunks */
1290  /* Initial buffer size must be sufficient for two pointers */
1291  if(!bsize) { bsize = sizeof(const char*); }
1292  p = api_posix_realloc((void*) res, bsize *= (size_t) 2);
1293  if(NULL == p) { err = 1; break; }
1294  res = p;
1295  }
1296  res[asize - (size_t) 1] = (const char*) group;
1297  res[asize++] = NULL;
1298  }
1299  p_flag = 0;
1300  continue;
1301  }
1302  else { if(!p_flag) { ns = i; p_flag = 1; } }
1303  }
1304  while(body[i++]);
1305 
1306  /* Release memory if not successful */
1307  if(err)
1308  {
1309  api_posix_free((void*) group);
1310  if(res)
1311  {
1312  for(i = 0; i < asize; ++i) { api_posix_free((void*) res[i]); }
1313  api_posix_free((void*) res);
1314  res = NULL;
1315  }
1316  }
1317 
1318  return(res);
1319 }
1320 
1321 
1322 /* ========================================================================== */
1323 /* Extract Message-IDs from 'References' header field */
1324 /*
1325  * RFC 5536 forbids that Message-IDs are longer than 250 octets.
1326  *
1327  * The return value is 'NULL' on error or a pointer to an array of strings
1328  * otherwise. Because the parameter 'body' is allowed to have arbitrary size,
1329  * this array has arbitrary size too. The array is terminated with a 'NULL'
1330  * pointer. The caller is responsible to free the memory for the array.
1331  */
1332 
1333 static const char** extract_refs(const char* body)
1334 {
1335  const char** res = NULL;
1336  size_t i = 0;
1337  int p_flag = 0; /* Indicates processing of msg-id is in progress */
1338  size_t ns = 0; /* Start index */
1339  size_t ne; /* End index (points behind the last character) */
1340  char* msgid;
1341  size_t len;
1342  int err = 0;
1343  size_t asize = 1; /* Current size of reference array (in elements) */
1344  size_t bsize = 0; /* Current size of memory block (in octets) */
1345  const char** p;
1346  int cfws_warn = 1;
1347 
1348  /*
1349  * Note:
1350  * The header parser has already unfolded the body and replaced CFWS with SP
1351  */
1352  /* printf("\n%s\n", body); */
1353  do
1354  {
1355  /* Check for CFWS or end of body */
1356  /* The check for '<' is used for error tolerance if CFWS is missing */
1357  if(!body[i] || ' ' == body[i] || '<' == body[i])
1358  {
1359  if(p_flag)
1360  {
1361  p_flag = 0;
1362  ne = i;
1363  if(!ne || ns >= ne)
1364  {
1365  PRINT_ERROR("Header parser: "
1366  "Invalid index in References (Bug)");
1367  continue;
1368  }
1369  else
1370  {
1371  len = ne - ns;
1372  if((size_t) 2 > len || (size_t) 250 < len)
1373  {
1374  PRINT_ERROR("Header parser: "
1375  "Invalid length of MID in References");
1376  continue;
1377  }
1378  if('>' != body[ne - (size_t) 1]) { continue; }
1379  }
1380  /* Extract Message-ID */
1381  if(cfws_warn && '<' == body[i])
1382  {
1383  PRINT_ERROR("Header parser: CFWS missing in References");
1384  cfws_warn = 0;
1385  }
1386  msgid = (char*) api_posix_malloc(len + (size_t) 1);
1387  if(NULL == msgid) { err = 1; break; }
1388  memcpy(msgid, &body[ns], len);
1389  msgid[len] = 0;
1390  /* printf("Ref: %s\n", msgid); */
1391  /* Add pointer to array */
1392  if(API_POSIX_SIZE_MAX == asize) { break; }
1393  len = (asize + (size_t) 1) * sizeof(const char*);
1394  if(len >= bsize)
1395  {
1396  /* Allocate more memory in exponentially increasing chunks */
1397  /* Initial buffer size must be sufficient for two pointers */
1398  if(!bsize) { bsize = sizeof(const char*); }
1399  p = api_posix_realloc((void*) res, bsize *= (size_t) 2);
1400  if(NULL == p) { break; }
1401  res = p;
1402  }
1403  res[asize - (size_t) 1] = (const char*) msgid;
1404  res[asize++] = NULL;
1405  }
1406  }
1407  /* Check for start of next Message-ID */
1408  if(!p_flag)
1409  {
1410  if('<' == body[i]) { ns = i; p_flag = 1; }
1411  }
1412  }
1413  while(body[i++]);
1414 
1415  /* Release memory if not successful */
1416  if(res && err)
1417  {
1418  for(i = 0; i < asize; ++i) { api_posix_free((void*) res[i]); }
1419  api_posix_free((void*) res);
1420  res = NULL;
1421  }
1422 
1423  return(res);
1424 }
1425 
1426 
1427 /* ========================================================================== */
1428 /* Destroy article header object */
1429 
1430 static void header_object_destructor(struct core_article_header** ahp)
1431 {
1432  size_t i;
1433 
1434  if(NULL != *ahp)
1435  {
1436  /* Delete header object content */
1437  api_posix_free((void*) (*ahp)->msgid);
1438  if(NULL != (*ahp)->groups)
1439  {
1440  i = 0;
1441  while(NULL != (*ahp)->groups[i])
1442  {
1443  api_posix_free((void*) (*ahp)->groups[i++]);
1444  }
1445  api_posix_free((void*) (*ahp)->groups);
1446  }
1447  api_posix_free((void*) (*ahp)->from);
1448  api_posix_free((void*) (*ahp)->subject);
1449  /* Note: Date field is part of the object structure */
1450  api_posix_free((void*) (*ahp)->supers);
1451  api_posix_free((void*) (*ahp)->fup2);
1452  api_posix_free((void*) (*ahp)->reply2);
1453  api_posix_free((void*) (*ahp)->uagent);
1454  api_posix_free((void*) (*ahp)->org);
1455  if(NULL != (*ahp)->refs)
1456  {
1457  i = 0;
1458  while(NULL != (*ahp)->refs[i])
1459  {
1460  api_posix_free((void*) (*ahp)->refs[i++]);
1461  }
1462  api_posix_free((void*) (*ahp)->refs);
1463  }
1464  api_posix_free((void*) (*ahp)->dist);
1465  api_posix_free((void*) (*ahp)->mime_v);
1466  api_posix_free((void*) (*ahp)->mime_ct);
1467  api_posix_free((void*) (*ahp)->mime_cte);
1468  api_posix_free((void*) (*ahp)->mime_cd);
1469  api_posix_free((void*) (*ahp)->x_newsr);
1470  api_posix_free((void*) (*ahp)->x_mailer);
1471  api_posix_free((void*) (*ahp)->x_pagent);
1472  /* Note: Lines field is part of the object structure */
1473 
1474  /* Delete header object structure */
1475  api_posix_free((void*) *ahp);
1476  *ahp = NULL;
1477  }
1478 }
1479 
1480 
1481 /* ========================================================================== */
1482 /* Create article header object */
1483 /*
1484  * If the same header field is found by the parser multiple times, the first
1485  * one is used and all others are ignored.
1486  */
1487 
1488 /* Allocates an empty string */
1489 #define CORE_GET_EMPTY_STRING(p) \
1490 { \
1491  p = (char*) api_posix_malloc(1); \
1492  if(NULL == p) { res = -1; } \
1493  else { ((char*) p)[0] = 0; } \
1494 }
1495 /* \attention Adjust the buffer size if you change the error message! */
1496 #define CORE_GET_ERROR_STRING(p) \
1497 { \
1498  p = (char*) api_posix_malloc(34); \
1499  if(NULL == p) { res = -1; } \
1500  else { strcpy((char*) p, "[Missing or invalid header field]"); } \
1501 }
1502 static int header_object_constructor(struct core_article_header** ahp,
1503  const char* h)
1504 {
1505  int res;
1506  struct core_headerfield* e = NULL;
1507  size_t i = 0;
1508  int rv;
1509  const char* mime_s;
1510  int mime = 0;
1511 
1512  res = header_parser(&e, h);
1513  if(!res)
1514  {
1515  /* Allocate memory for header fields */
1516  *ahp = (struct core_article_header*)
1517  api_posix_malloc(sizeof(struct core_article_header));
1518  if(NULL == *ahp) { res = -1; }
1519  else
1520  {
1521  /* Init header fields */
1522  /* Mandatory header fields */
1523  (*ahp)->msgid = NULL;
1524  (*ahp)->groups = NULL;
1525  (*ahp)->from = NULL;
1526  (*ahp)->subject = NULL;
1527  (*ahp)->date = 0;
1528  /* Optional header fields */
1529  (*ahp)->supers = NULL;
1530  (*ahp)->fup2 = NULL;
1531  (*ahp)->reply2 = NULL;
1532  (*ahp)->uagent = NULL;
1533  (*ahp)->org = NULL;
1534  (*ahp)->refs = NULL;
1535  (*ahp)->dist = NULL;
1536  (*ahp)->mime_v = NULL;
1537  (*ahp)->mime_ct = NULL;
1538  (*ahp)->mime_cte = NULL;
1539  (*ahp)->mime_cd = NULL;
1540  (*ahp)->x_newsr = NULL;
1541  (*ahp)->x_mailer = NULL;
1542  (*ahp)->x_pagent = NULL;
1543  /* Obsolete header fields */
1544  (*ahp)->lines = 0;
1545 
1546  /* Insert all fields that the parser have found */
1547  while(CORE_HDR_EOH != e[i].id)
1548  {
1549  switch(e[i].id)
1550  {
1551  case CORE_HDR_MSGID:
1552  {
1553  if(!(*ahp)->msgid)
1554  {
1555  (*ahp)->msgid = e[i].content;
1556  e[i].content = NULL;
1557  }
1558  break;
1559  }
1560  case CORE_HDR_GROUPS:
1561  {
1562  if(!(*ahp)->groups)
1563  {
1564  (*ahp)->groups = extract_groups(e[i].content);
1565  /* New memory was allocated, don't preserve 'content' */
1566  }
1567  break;
1568  }
1569  case CORE_HDR_FROM:
1570  {
1571  if(!(*ahp)->from)
1572  {
1573  rv = enc_mime_word_decode(&mime_s, e[i].content);
1574  if(!rv) { mime = 1; }
1575  else { mime_s = e[i].content; e[i].content = NULL; }
1576  (*ahp)->from = mime_s;
1577  }
1578  break;
1579  }
1580  case CORE_HDR_SUBJECT:
1581  {
1582  if(!(*ahp)->subject)
1583  {
1584  rv = enc_mime_word_decode(&mime_s, e[i].content);
1585  if(!rv) { mime = 1; }
1586  else { mime_s = e[i].content; e[i].content = NULL; }
1587  (*ahp)->subject = mime_s;
1588  }
1589  break;
1590  }
1591  case CORE_HDR_DATE:
1592  {
1593  if(!(*ahp)->date)
1594  {
1595  (*ahp)->date = enc_timestamp_decode(e[i].content);
1596  }
1597  break;
1598  }
1599  case CORE_HDR_SUPERS:
1600  {
1601  if(!(*ahp)->supers)
1602  {
1603  (*ahp)->supers = e[i].content;
1604  e[i].content = NULL;
1605  }
1606  break;
1607  }
1608  case CORE_HDR_FUP2:
1609  {
1610  if(!(*ahp)->fup2)
1611  {
1612  (*ahp)->fup2 = e[i].content;
1613  e[i].content = NULL;
1614  }
1615  break;
1616  }
1617  case CORE_HDR_REPLY2:
1618  {
1619  if(!(*ahp)->reply2)
1620  {
1621  rv = enc_mime_word_decode(&mime_s, e[i].content);
1622  if(!rv) { mime = 1; }
1623  else { mime_s = e[i].content; e[i].content = NULL; }
1624  (*ahp)->reply2 = mime_s;
1625  }
1626  break;
1627  }
1628  case CORE_HDR_UAGENT:
1629  {
1630  if(!(*ahp)->uagent)
1631  {
1632  rv = enc_mime_word_decode(&mime_s, e[i].content);
1633  if(!rv) { mime = 1; }
1634  else { mime_s = e[i].content; e[i].content = NULL; }
1635  (*ahp)->uagent = mime_s;
1636  }
1637  break;
1638  }
1639  case CORE_HDR_ORG:
1640  {
1641  if(!(*ahp)->org)
1642  {
1643  rv = enc_mime_word_decode(&mime_s, e[i].content);
1644  if(!rv) { mime = 1; }
1645  else { mime_s = e[i].content; e[i].content = NULL; }
1646  (*ahp)->org = mime_s;
1647  }
1648  break;
1649  }
1650  case CORE_HDR_REFS:
1651  {
1652  if(!(*ahp)->refs)
1653  {
1654  (*ahp)->refs = extract_refs(e[i].content);
1655  /* New memory was allocated, don't preserve 'content' */
1656  }
1657  break;
1658  }
1659  case CORE_HDR_DIST:
1660  {
1661  if(!(*ahp)->dist)
1662  {
1663  (*ahp)->dist = e[i].content;
1664  e[i].content = NULL;
1665  }
1666  break;
1667  }
1668  case CORE_HDR_MIME:
1669  {
1670  if(!(*ahp)->mime_v)
1671  {
1672  (*ahp)->mime_v = e[i].content;
1673  e[i].content = NULL;
1674  }
1675  break;
1676  }
1677  case CORE_HDR_CT:
1678  {
1679  if(!(*ahp)->mime_ct)
1680  {
1681  rv = enc_mime_para_decode(&mime_s, e[i].content, 1);
1682  if(!rv) { mime = 1; }
1683  else { mime_s = e[i].content; e[i].content = NULL; }
1684  (*ahp)->mime_ct = mime_s;
1685  }
1686 
1687  break;
1688  }
1689  case CORE_HDR_CTE:
1690  {
1691  if(!(*ahp)->mime_cte)
1692  {
1693  (*ahp)->mime_cte = e[i].content;
1694  e[i].content = NULL;
1695  }
1696  break;
1697  }
1698  case CORE_HDR_CD:
1699  {
1700  if(!(*ahp)->mime_cd)
1701  {
1702  rv = enc_mime_para_decode(&mime_s, e[i].content, 0);
1703  if(rv) { mime_s = e[i].content; e[i].content = NULL; }
1704  (*ahp)->mime_cd = mime_s;
1705  }
1706  break;
1707  }
1708  case CORE_HDR_X_NEWSR:
1709  {
1710  if(!(*ahp)->x_newsr)
1711  {
1712  rv = enc_mime_word_decode(&mime_s, e[i].content);
1713  if(!rv) { mime = 1; }
1714  else { mime_s = e[i].content; e[i].content = NULL; }
1715  (*ahp)->x_newsr = mime_s;
1716  }
1717  break;
1718  }
1719  case CORE_HDR_X_MAILER:
1720  {
1721  if(!(*ahp)->x_mailer)
1722  {
1723  rv = enc_mime_word_decode(&mime_s, e[i].content);
1724  if(!rv) { mime = 1; }
1725  else { mime_s = e[i].content; e[i].content = NULL; }
1726  (*ahp)->x_mailer = mime_s;
1727  }
1728  break;
1729  }
1730  case CORE_HDR_X_PAGENT:
1731  {
1732  if(!(*ahp)->x_pagent)
1733  {
1734  rv = enc_mime_word_decode(&mime_s, e[i].content);
1735  if(!rv) { mime = 1; }
1736  else { mime_s = e[i].content; e[i].content = NULL; }
1737  (*ahp)->x_pagent = mime_s;
1738  }
1739  break;
1740  }
1741  case CORE_HDR_LINES:
1742  {
1743  if(!(*ahp)->lines)
1744  {
1745  (*ahp)->lines = enc_lines_decode(e[i].content);
1746  }
1747  break;
1748  }
1749  default:
1750  {
1751  PRINT_ERROR("Invalid header field ID");
1752  break;
1753  }
1754  }
1755  ++i;
1756  }
1757  }
1758 
1759  /* Delete remaining parser results */
1760  i = 0;
1761  while(CORE_HDR_EOH != e[i].id)
1762  {
1763  api_posix_free((void*) e[i++].content);
1764  }
1765  api_posix_free((void*) e);
1766 
1767  /* Ensure that all mandatory header fields contain valid stings */
1768  if(!res)
1769  {
1770  if(NULL == (*ahp)->msgid) { CORE_GET_EMPTY_STRING((*ahp)->msgid); }
1771  if(NULL == (*ahp)->groups)
1772  {
1773  (*ahp)->groups = (const char**)
1774  api_posix_malloc(sizeof(const char*) * (size_t) 2);
1775  if(NULL != (*ahp)->groups)
1776  {
1777  CORE_GET_EMPTY_STRING((*ahp)->groups[0]);
1778  (*ahp)->groups[1] = NULL;
1779  }
1780  }
1781  if(NULL == (*ahp)->from) { CORE_GET_ERROR_STRING((*ahp)->from); }
1782  if(NULL == (*ahp)->subject)
1783  {
1784  CORE_GET_ERROR_STRING((*ahp)->subject);
1785  }
1786  }
1787 
1788  /* Destroy unfinished object on error */
1789  if(res) { header_object_destructor(ahp); }
1790  }
1791 
1792  /* Check for MIME format without MIME declaration */
1793  if(!res && mime)
1794  {
1795  if(!(*ahp)->mime_v)
1796  {
1797  PRINT_ERROR("Header parser: "
1798  "MIME-Version field missing, but MIME is used");
1799  }
1800  }
1801 
1802  return(res);
1803 }
1804 
1805 
1806 /* ========================================================================== */
1807 /* Create new hierarchy element object */
1808 
1809 static int hierarchy_element_constructor(struct core_hierarchy_element** he,
1810  core_anum_t num, unsigned int flags)
1811 {
1812  int res = 0;
1813 
1814  /* Allocate hierarchy element structure */
1815  *he = (struct core_hierarchy_element*)
1816  api_posix_malloc(sizeof(struct core_hierarchy_element));
1817  if(NULL == *he) { res = -1; }
1818  else
1819  {
1820  /* Init all fields of structure (all pointers to 'NULL') */
1821  (*he)->anum = num; /* Article number 0 is reserved */
1822  (*he)->flags = flags;
1823  (*he)->header = NULL;
1824  (*he)->parent = NULL;
1825  (*he)->children = 0;
1826  (*he)->child = NULL;
1827  }
1828 
1829  return(res);
1830 }
1831 
1832 
1833 /* ========================================================================== */
1834 /* Destroy hierarchy element object */
1835 
1836 static void hierarchy_element_destructor(struct core_hierarchy_element** he)
1837 {
1838  if(NULL != *he)
1839  {
1840  /* Destroy header object */
1841  header_object_destructor(&(*he)->header);
1842  /* Delete child array */
1843  api_posix_free((void*) (*he)->child);
1844  /* Delete hierarchy element structure */
1845  api_posix_free((void*) *he);
1846  *he = NULL;
1847  }
1848 }
1849 
1850 
1851 /* ========================================================================== */
1852 /* Initialize new (sub)hierarchy */
1853 /*
1854  * Deletes the current hierarchy and return with only the root node.
1855  *
1856  * For deep hierarchies this algorithm is slow, but memory consumption is very
1857  * low (no recursion).
1858  */
1859 
1860 static int hierarchy_init(struct core_hierarchy_element** root)
1861 {
1862  int res = 0;
1863  struct core_hierarchy_element** current;
1864 
1865  if(!root) { res = -1; }
1866  else
1867  {
1868  /* Delete old article hierarchy */
1869  while(*root)
1870  {
1871  /* Process one leaf node per loop */
1872  current = root;
1873  while((*current)->children)
1874  {
1875  /* Select last child in array */
1876  current = &(*current)->child[(*current)->children - (size_t) 1];
1877  }
1878  if((*current)->parent) { (*current)->parent->children--; }
1879  hierarchy_element_destructor(current);
1880  }
1881  /* Create new root node */
1882  res = hierarchy_element_constructor(root, 0, 0);
1883  }
1884 
1885  return(res);
1886 }
1887 
1888 
1889 /* ========================================================================== */
1890 /* Add article to (sub)hierarchy */
1891 
1892 static struct core_hierarchy_element*
1893 hierarchy_find_article(const char* msgid, struct core_hierarchy_element* root)
1894 {
1895  struct core_hierarchy_element* res = root;
1896  struct core_hierarchy_element* tmp;
1897  size_t i;
1898 
1899  /* Check children of root node */
1900  for(i = 0; i < root->children; ++i)
1901  {
1902  if(!strcmp(msgid, root->child[i]->header->msgid))
1903  {
1904  res = root->child[i];
1905  break;
1906  }
1907  else
1908  {
1909  /* Recursively search for parent article if parent was not found */
1910  tmp = hierarchy_find_article(msgid, root->child[i]);
1911  if(tmp != root->child[i])
1912  {
1913  res = tmp;
1914  break;
1915  }
1916  }
1917  }
1918 
1919  return(res);
1920 }
1921 
1922 static int hierarchy_sort_children(const struct core_hierarchy_element** a,
1923  const struct core_hierarchy_element** b)
1924 {
1925  int res = 0;
1926  core_time_t a_date = (*a)->header->date;
1927  core_time_t b_date = (*b)->header->date;
1928 
1929  if(!config[CONF_INV_ORDER].val.i)
1930  {
1931  /* Normal order */
1932  if(a_date < b_date) { res = -1; }
1933  if(a_date > b_date) { res = 1; }
1934  }
1935  else
1936  {
1937  /* Inverted order */
1938  if(a_date < b_date) { res = 1; }
1939  if(a_date > b_date) { res = -1; }
1940  }
1941 
1942  return(res);
1943 }
1944 
1945 static int hierarchy_add(struct core_hierarchy_element** root,
1946  core_anum_t num, unsigned int flags,
1947  const char* header)
1948 {
1949  int res = 0;
1950  struct core_hierarchy_element* he_new = NULL;
1951  struct core_hierarchy_element** he_tmp = NULL;
1952  struct core_hierarchy_element* he_parent = *root;
1953  struct core_hierarchy_element* he_super = NULL;
1954  size_t size;
1955  size_t i = 0;
1956  const char supers_subject[] = "[Superseded]";
1957  char* tmp;
1958  int rv;
1959  char an[17];
1960  size_t an_len;
1961 
1962  /* Fail if root node doesn't exist */
1963  if(NULL == *root) { res = -1; }
1964 
1965  /* Create new article node */
1966  if(!res)
1967  {
1968  res = hierarchy_element_constructor(&he_new, num, flags);
1969  /* Process header */
1970  if(!res)
1971  {
1972  res = header_object_constructor(&he_new->header, header);
1973  if(!res && main_debug)
1974  {
1975  rv = enc_convert_anum_to_ascii(an, &an_len, num);
1976  if(!rv)
1977  {
1978  printf("%s: %sAdded article %s to hierarchy: %s\n",
1979  CFG_NAME, MAIN_ERR_PREFIX, an, he_new->header->msgid);
1980  }
1981  }
1982  }
1983  /* Check for error */
1984  if(0 > res) { api_posix_free((void*) he_new); }
1985  }
1986 
1987  /* Check for supersede */
1988  if(!res)
1989  {
1990  if(NULL != he_new->header->supers)
1991  {
1992  /* Search for superseded article */
1993  he_super = hierarchy_find_article(he_new->header->supers, *root);
1994  if(he_super != *root)
1995  {
1996  /* Replace subject of superseded article */
1997  size = strlen(supers_subject);
1998  tmp = (char*) api_posix_realloc((void*) he_super->header->subject,
1999  ++size);
2000  if(NULL == tmp)
2001  {
2002  PRINT_ERROR("Memory allocation for subject field failed");
2003  }
2004  else
2005  {
2006  strcpy(tmp, supers_subject);
2007  he_super->header->subject = tmp;
2008  }
2009  }
2010  }
2011  }
2012 
2013  /* Search for parent article, otherwise add to root node */
2014  if(!res)
2015  {
2016  /* Find parent article if the new article has references */
2017  if(NULL != he_new->header->refs)
2018  {
2019  /* Search for last reference first */
2020  while(NULL != he_new->header->refs[i]) { ++i; };
2021  while(i)
2022  {
2023  he_parent = hierarchy_find_article(he_new->header->refs[--i],
2024  *root);
2025  if(he_parent != *root) { break; }
2026  }
2027  }
2028  /* Increase size of parents child array */
2029  size = (he_parent->children + (size_t) 1)
2030  * sizeof(struct core_hierarchy_element*);
2031  he_tmp = (struct core_hierarchy_element**)
2032  api_posix_realloc(he_parent->child, size);
2033  if(NULL == he_tmp) { res = -1; }
2034  /* Insert new node */
2035  if(res) { hierarchy_element_destructor(&he_new); }
2036  else
2037  {
2038  he_parent->child = he_tmp;
2039  he_new->parent = he_parent;
2040  he_parent->child[he_parent->children++] = he_new;
2041  /* Sort array of children */
2042  qsort((void*) &he_parent->child[0], he_parent->children,
2043  sizeof(struct core_hierarchy_element*),
2044  (int (*)(const void*, const void*)) hierarchy_sort_children);
2045  }
2046  }
2047 
2048  return(res);
2049 }
2050 
2051 
2052 /* ========================================================================== */
2053 /* Update element in (sub)hierarchy */
2054 
2055 static struct core_hierarchy_element*
2056 hierarchy_find_element(core_anum_t num, struct core_hierarchy_element* root)
2057 {
2058  struct core_hierarchy_element* res = NULL;
2059  struct core_hierarchy_element* tmp;
2060  size_t i;
2061 
2062  /* Check children of root node */
2063  for(i = 0; i < root->children; ++i)
2064  {
2065  if(num == root->child[i]->anum)
2066  {
2067  res = root->child[i];
2068  break;
2069  }
2070  else
2071  {
2072  /* Recursively search for parent article if parent was not found */
2073  tmp = hierarchy_find_element(num, root->child[i]);
2074  if(tmp)
2075  {
2076  res = tmp;
2077  break;
2078  }
2079  }
2080  }
2081 
2082  return(res);
2083 }
2084 
2085 
2086 static int hierarchy_update(struct core_hierarchy_element** root,
2087  core_anum_t num, const char* header)
2088 {
2089  int res = 0;
2090  struct core_hierarchy_element* he = NULL;
2091  struct core_article_header* hdr = NULL;
2092 
2093  /* Fail if root node doesn't exist */
2094  if(NULL == *root) { res = -1; }
2095 
2096  /* Search for hierarchy element to replace */
2097  if(!res)
2098  {
2099  he = hierarchy_find_element(num, *root);
2100  if(NULL == he) { res = -1; }
2101  }
2102 
2103  /* Process header */
2104  if(!res) { res = header_object_constructor(&hdr, header); }
2105 
2106  /* Replace header object of hierarchy element */
2107  if(!res)
2108  {
2109  header_object_destructor(&he->header);
2110  he->header = hdr;
2111  }
2112 
2113  return(res);
2114 }
2115 
2116 
2117 /* ========================================================================== */
2118 /* Allocate and initialize nexus object */
2119 
2120 static int nexus_constructor(struct core_nexus** nexus, const char* server)
2121 {
2122  int res = -1;
2123  char* s;
2124 
2125  if(NULL == *nexus)
2126  {
2127  s = (char*) api_posix_malloc(strlen(server) + (size_t) 1);
2128  if(NULL != s)
2129  {
2130  strcpy(s, server);
2131  *nexus = (struct core_nexus*)
2132  api_posix_malloc(sizeof(struct core_nexus));
2133  if(NULL == *nexus) { api_posix_free((void*) s); }
2134  else
2135  {
2136  (*nexus)->nntp_state = CORE_NEXUS_CLOSED;
2137  (*nexus)->nntp_server = s;
2138  (*nexus)->nntp_handle = -1;
2139  (*nexus)->nntp_current_group = NULL;
2140  res = 0;
2141  }
2142  }
2143  }
2144 
2145  return(res);
2146 }
2147 
2148 
2149 /* ========================================================================== */
2150 /* Destroy nexus object */
2151 
2152 static void nexus_destructor(struct core_nexus** nexus)
2153 {
2154  if(NULL != *nexus)
2155  {
2156  if(NULL != (*nexus)->nntp_server)
2157  {
2158  api_posix_free((void*) (*nexus)->nntp_server);
2159  }
2160  if(NULL != (*nexus)->nntp_current_group)
2161  {
2162  api_posix_free((void*) (*nexus)->nntp_current_group);
2163  }
2164  api_posix_free((void*) *nexus);
2165  *nexus = NULL;
2166  }
2167 }
2168 
2169 
2170 /* ========================================================================== */
2171 /* Establish connection to news server */
2172 
2173 static int nexus_open(struct core_nexus** nexus)
2174 {
2175  int res = 0;
2176  const char* logpathname = log_get_logpathname();
2177  const char* service = config[CONF_SERVICE].val.s;
2178  int enc = config[CONF_ENC].val.i;
2179  int auth = config[CONF_AUTH].val.i;
2180  int immed = config[CONF_IMMEDAUTH].val.i;
2181  const char* user = config[CONF_USER].val.s;
2182  const char* pass = config[CONF_PASS].val.s;
2183 
2184  /* Enable protocol logfile for debug mode */
2185  if(main_debug)
2186  {
2187  printf("%s: %sProtocol logfile: %s\n",
2188  CFG_NAME, MAIN_ERR_PREFIX, logpathname);
2189  }
2190  else
2191  {
2192  /* Remove potential existing logfile */
2193  if(!fu_check_file(logpathname, NULL))
2194  {
2195  (void) fu_unlink_file(logpathname);
2196  }
2197  api_posix_free((void*) logpathname);
2198  logpathname = NULL;
2199  }
2200 
2201  /* Allocate new nexus if required */
2202  if(NULL == *nexus)
2203  {
2204  res = nexus_constructor(nexus, config[CONF_SERVER].val.s);
2205  }
2206  if(!res)
2207  {
2208  /* Connect to NNTP server */
2209  /* Check authentication algorithm */
2210  switch(auth)
2211  {
2212  case 0:
2213  {
2214  res = nntp_open(&(*nexus)->nntp_handle, (*nexus)->nntp_server,
2215  service, logpathname, enc, auth);
2216  break;
2217  }
2218  case 1:
2219  {
2220  if(!config[CONF_PASS].val.s[0])
2221  {
2222  PRINT_ERROR("Authentication with empty password rejected");
2223  res = -1;
2224  }
2225  else
2226  {
2227  res = nntp_open(&(*nexus)->nntp_handle, (*nexus)->nntp_server,
2228  service, logpathname, enc, auth,
2229  immed, user, pass);
2230  }
2231  break;
2232  }
2233  default:
2234  {
2235  PRINT_ERROR("Authentication algorithm not supported");
2236  res = -1;
2237  break;
2238  }
2239  }
2240  if (0 > res) { (*nexus)->nntp_state = CORE_NEXUS_CLOSED; }
2241  else { (*nexus)->nntp_state = CORE_NEXUS_ESTABLISHED; }
2242  }
2243 
2244  api_posix_free((void*) logpathname);
2245 
2246  return(res);
2247 }
2248 
2249 
2250 /* ========================================================================== */
2251 /* Close connection to news server
2252  *
2253  * \attention
2254  * This function is called by the core thread cleanup handler.
2255  */
2256 
2257 static void nexus_close(struct core_nexus** nexus)
2258 {
2259  if(NULL != *nexus)
2260  {
2261  /* Close connection to NNTP server */
2262  if(-1 != (*nexus)->nntp_handle)
2263  {
2264  nntp_close(&(*nexus)->nntp_handle, 0);
2265  }
2266  /* Destroy nexus */
2267  nexus_destructor(nexus);
2268  }
2269 
2270  return;
2271 }
2272 
2273 
2274 /* ========================================================================== */
2275 /* Try to establish a nexus */
2276 
2277 static int nexus_handler(struct core_nexus** nexus)
2278 {
2279  int res = 0;
2280 
2281  /* Check whether nexus exist and is in established state */
2282  if(NULL == *nexus) { res = -1; }
2283  else if(CORE_NEXUS_ESTABLISHED != (*nexus)->nntp_state) { res = -1; }
2284  /* Establish nexus if required */
2285  if(res)
2286  {
2287  res = nexus_open(nexus);
2288  if(!res)
2289  {
2290  /* Set current group if there is one */
2291  if(NULL != (*nexus)->nntp_current_group)
2292  {
2293  nntp_set_group((*nexus)->nntp_handle, (*nexus)->nntp_current_group,
2294  NULL);
2295  }
2296  }
2297  }
2298 
2299  return(res);
2300 }
2301 
2302 
2303 /* ========================================================================== */
2304 /* Check connection to server
2305  *
2306  * Close nexus if transport subsystem report broken connection.
2307  * Check whether authentication failed.
2308  *
2309  * \return
2310  * - Nonzero to indicate abort request
2311  */
2312 
2313 static int check_connection(int r)
2314 {
2315  int res = 0;
2316 
2317  if(-2 == r)
2318  {
2319  PRINT_ERROR("Lost nexus, trying to recover");
2320  if(NULL != n)
2321  {
2322  nntp_close(&n->nntp_handle, NNTP_CLOSE_NOQUIT);
2323  n->nntp_state = CORE_NEXUS_CLOSED;
2324  }
2325  }
2326  else if(-3 == r) { res = -1; }
2327 
2328  return(res);
2329 }
2330 
2331 
2332 /* ========================================================================== */
2333 /* Get message of the day */
2334 
2335 static void get_motd(void)
2336 {
2337  int res;
2338  size_t len = 0;
2339  char* motd = NULL;
2340  unsigned int retries = CORE_NEXUS_RETRIES;
2341 
2342  do
2343  {
2344  res = nexus_handler(&n);
2345  if(!res)
2346  {
2347  res = nntp_get_motd(n->nntp_handle, &motd, &len);
2348  if(!res)
2349  {
2350  /* Store result */
2351  data.data = (void*) motd;
2352  data.size = len;
2353  }
2354  }
2355  if(check_connection(res)) { break; }
2356  }
2357  while(res && retries--);
2358  data.result = res;
2359 }
2360 
2361 
2362 /* ========================================================================== */
2363 /* Get distribution patterns */
2364 
2365 static void get_distrib_pats(void)
2366 {
2367  int res;
2368  size_t len = 0;
2369  const char* d_pats = NULL;
2370  unsigned int retries = CORE_NEXUS_RETRIES;
2371 
2372  do
2373  {
2374  res = nexus_handler(&n);
2375  if(!res)
2376  {
2377  res = nntp_get_distrib_pats(n->nntp_handle, &d_pats, &len);
2378  if(!res)
2379  {
2380  /* Store result */
2381  data.data = (void*) d_pats;
2382  data.size = len;
2383  }
2384  }
2385  if(check_connection(res)) { break; }
2386  }
2387  while(res && retries--);
2388  data.result = res;
2389 }
2390 
2391 
2392 /* ========================================================================== */
2393 /* Get subscriptions */
2394 
2395 static void get_subscriptions(void)
2396 {
2397  int res;
2398  size_t len = 0;
2399  char* subs = NULL;
2400  unsigned int retries = CORE_NEXUS_RETRIES;
2401 
2402  do
2403  {
2404  res = nexus_handler(&n);
2405  if(!res)
2406  {
2407  res = nntp_get_subscriptions(n->nntp_handle, &subs, &len);
2408  if(!res)
2409  {
2410  /* Store result */
2411  data.data = (void*) subs;
2412  data.size = len;
2413  }
2414  }
2415  if(check_connection(res)) { break; }
2416  }
2417  while(res && retries--);
2418  data.result = res;
2419 }
2420 
2421 
2422 /* ========================================================================== */
2423 /* Reset group states and article header cache */
2424 
2425 static void reset_group_states(void)
2426 {
2427  int res;
2428  int rv;
2429 
2430  nexus_close(&n);
2431  res = db_clear();
2432  rv = group_reset_states();
2433  if(!res) { res = rv; }
2434 
2435  data.result = res;
2436 }
2437 
2438 
2439 /* ========================================================================== */
2440 /* Get list of available newsgroups */
2441 
2442 static void get_group_list(void)
2443 {
2444  int res;
2445  size_t gc;
2446  struct nntp_groupdesc* groups = NULL;
2447  unsigned int retries = CORE_NEXUS_RETRIES;
2448 
2449  do
2450  {
2451  res = nexus_handler(&n);
2452  if(!res)
2453  {
2454  res = nntp_get_grouplist(n->nntp_handle, &gc, &groups);
2455  if(!res)
2456  {
2457  data.data = (void*) groups;
2458  data.size = gc;
2459  }
2460  }
2461  if(check_connection(res)) { break; }
2462  }
2463  while(0 > res && retries--);
2464  data.result = res;
2465 }
2466 
2467 
2468 /* ========================================================================== */
2469 /* Get list of newsgroup labels */
2470 
2471 static void get_group_labels(void)
2472 {
2473  int res;
2474  size_t gc;
2475  struct nntp_grouplabel* labels = NULL;
2476  unsigned int retries = CORE_NEXUS_RETRIES;
2477 
2478  do
2479  {
2480  res = nexus_handler(&n);
2481  if(!res)
2482  {
2483  res = nntp_get_group_labels(n->nntp_handle, &gc, &labels);
2484  if(!res)
2485  {
2486  data.data = (void*) labels;
2487  data.size = gc;
2488  }
2489  }
2490  if(check_connection(res)) { break; }
2491  }
2492  while(0 > res && retries--);
2493  data.result = res;
2494 }
2495 
2496 
2497 /* ========================================================================== */
2498 /* Get information about multiple newsgroups */
2499 
2500 static void get_groupinfo(void)
2501 {
2502  int res = -1;
2503  size_t gc;
2504  struct core_groupstate** gs;
2505  struct nntp_groupdesc* gd = NULL;
2506  struct nntp_groupdesc* garray = NULL;
2507  const char** gl;
2508  size_t i;
2509  unsigned int retries = CORE_NEXUS_RETRIES;
2510 
2511  /* Extract data about groups to query */
2512  gc = data.size;
2513  gs = (struct core_groupstate**) data.data;
2514  if(!gc)
2515  {
2516  /* Delete article header cache for all groups */
2517  db_update_groups(0, NULL);
2518  data.data = (void*) NULL;
2519  res = 0;
2520  }
2521  else
2522  {
2523  /* Delete article header cache for all groups that are not listed */
2524  gl = (const char**) api_posix_malloc(gc * sizeof(const char*));
2525  if(NULL != gl)
2526  {
2527  for(i = 0; i < gc; ++i) { gl[i] = (*gs)[i].name; }
2528  db_update_groups(gc, gl);
2529  api_posix_free((void*) gl);
2530  }
2531  do
2532  {
2533  res = nexus_handler(&n);
2534  if(!res)
2535  {
2536  /* Allocate memory for information object */
2537  garray = (struct nntp_groupdesc*)
2538  api_posix_malloc(gc * sizeof(struct nntp_groupdesc));
2539  if(NULL == garray) { res = -1; break; }
2540  /* Get group information */
2541  for(i = 0; i < gc; ++i)
2542  {
2543  /* printf("Query info for group: %s\n", (*gs)[i].name); */
2544  res = nntp_set_group(n->nntp_handle, (*gs)[i].name, &gd);
2545  if(0 > res)
2546  {
2547  /* Check for lost nexus */
2548  if(-2 == res) { break; }
2549  /* Check for failed authentication */
2550  if(-3 == res)
2551  {
2552  /* Give up */
2553  retries = 0;
2554  break;
2555  }
2556  /* Group not available => Mark as empty */
2557  gd = nntp_group_descriptor_constructor((*gs)[i].name);
2558  if(NULL == gd) { break; }
2559  else { res = 0; }
2560  }
2561  memcpy((void*) &garray[i], (void*) gd,
2562  sizeof(struct nntp_groupdesc));
2563  /* Update descriptor (because we have copied the name) */
2564  memcpy((void*) &garray[i].name, (void*) &(*gs)[i].name,
2565  sizeof(const char*));
2566  api_posix_free((void*) gd);
2567  }
2568  }
2569  if(0 > res)
2570  {
2571  api_posix_free((void*) garray);
2572  garray = NULL;
2573  }
2574  else { data.data = (void*) garray; }
2575  if(check_connection(res)) { break; }
2576  }
2577  while(0 > res && retries--);
2578  }
2579  data.result = res;
2580 }
2581 
2582 
2583 /* ========================================================================== */
2584 /* Set current group */
2585 
2586 static void set_group(void)
2587 {
2588  int res;
2589  struct nntp_groupdesc* gd = NULL;
2590  char* gn;
2591  size_t len;
2592  unsigned int retries = CORE_NEXUS_RETRIES;
2593  core_anum_t socr; /* Start of current article watermark range */
2594 
2595  do
2596  {
2597  res = nexus_handler(&n);
2598  if(!res)
2599  {
2600  res = nntp_set_group(n->nntp_handle, (const char*) data.data, &gd);
2601  if(!res)
2602  {
2603  /*
2604  * Store current group
2605  * This is necessary to be able to reestablish the state if the
2606  * connection to the server gets broken.
2607  */
2608  len = strlen(data.data);
2609  gn = (char*) api_posix_malloc(++len);
2610  if(NULL == gn) { n->nntp_current_group = NULL; }
2611  else
2612  {
2613  strcpy(gn, data.data);
2614  if(NULL != n->nntp_current_group)
2615  {
2616  api_posix_free((void*) n->nntp_current_group);
2617  }
2618  n->nntp_current_group = (const char*) gn;
2619  }
2620  /* Clamp range of local database for group to current range */
2621  socr = (core_anum_t) gd->lwm;
2622  if((core_anum_t) 1 < socr)
2623  {
2624  db_delete(gd->name, (core_anum_t) 1, --socr);
2625  }
2626  /* Prepare result */
2627  data.size = 1;
2628  data.data = (void*) gd;
2629  }
2630  }
2631  if(check_connection(res)) { break; }
2632  }
2633  while(res && retries--);
2634  data.result = res;
2635 }
2636 
2637 
2638 /* ========================================================================== */
2639 /* Get article header overview */
2640 
2641 static void get_overview(void)
2642 {
2643  int res;
2644  struct core_range* range = (struct core_range*) data.data;
2645  size_t len = 0;
2646  char* overview = NULL;
2647  unsigned int retries = CORE_NEXUS_RETRIES;
2648 
2649  do
2650  {
2651  res = nexus_handler(&n);
2652  if(!res)
2653  {
2654  res = nntp_get_overview(n->nntp_handle, range->first, range->last,
2655  &overview, &len);
2656  if(!res)
2657  {
2658  /* Store result */
2659  data.data = (void*) overview;
2660  data.size = len;
2661  }
2662  }
2663  if(check_connection(res)) { break; }
2664  }
2665  while(res && retries--);
2666  data.result = res;
2667 }
2668 
2669 
2670 /* ========================================================================== */
2671 /* Get complete article via Message-ID */
2672 
2673 static void get_article_by_mid(void)
2674 {
2675  int res;
2676  size_t len = 0;
2677  char* article = NULL;
2678  unsigned int retries = CORE_NEXUS_RETRIES;
2679 
2680  do
2681  {
2682  res = nexus_handler(&n);
2683  if(!res)
2684  {
2685  res = nntp_get_article_by_mid(n->nntp_handle, (const char*) data.data,
2686  &article, &len);
2687  if(!res)
2688  {
2689  /* Store result */
2690  data.data = (void*) article;
2691  data.size = len;
2692  }
2693  }
2694  if(check_connection(res)) { break; }
2695  }
2696  while(res && retries--);
2697  data.result = res;
2698 }
2699 
2700 
2701 /* ========================================================================== */
2702 /* Get complete article */
2703 
2704 static void get_article(void)
2705 {
2706  int res;
2707  size_t len = 0;
2708  char* article = NULL;
2709  unsigned int retries = CORE_NEXUS_RETRIES;
2710 
2711  do
2712  {
2713  res = nexus_handler(&n);
2714  if(!res)
2715  {
2716  res = nntp_get_article(n->nntp_handle, (const nntp_anum_t*) data.data,
2717  &article, &len);
2718  if(!res)
2719  {
2720  /* Store result */
2721  data.data = (void*) article;
2722  data.size = len;
2723  }
2724  }
2725  if(check_connection(res)) { break; }
2726  }
2727  while(res && retries--);
2728  data.result = res;
2729 }
2730 
2731 
2732 /* ========================================================================== */
2733 /* Get article header */
2734 
2735 static void get_article_header(void)
2736 {
2737  int res;
2738  size_t len = 0;
2739  char* header = NULL;
2740  core_anum_t* anum = (core_anum_t*) data.data;
2741  unsigned int retries = CORE_NEXUS_RETRIES;
2742 
2743  /* Check whether requested article header is in local database */
2744  res = db_read(n->nntp_current_group, *anum, &header, &len);
2745  if(!res)
2746  {
2747  /* Yes */
2748  data.data = (void*) header;
2749  data.size = len;
2750  }
2751  else
2752  {
2753  /* No => Fetch it from server */
2754  do
2755  {
2756  res = nexus_handler(&n);
2757  if(!res)
2758  {
2759  res = nntp_get_article_header(n->nntp_handle,
2760  (const nntp_anum_t*) anum,
2761  &header, &len);
2762  if(!res)
2763  {
2764  data.data = (void*) header;
2765  data.size = len;
2766  /*
2767  * Add header to local database
2768  * Note that 'len' is the buffer size, not the header length!
2769  */
2770  db_add(n->nntp_current_group, *anum, header, strlen(header));
2771  }
2772  else
2773  {
2774  /*
2775  * Can't retrieve article header
2776  * Return success anyhow because missing articles in the reported
2777  * range are allowed (the IDs of canceled articles are not
2778  * reassigned). The NULL pointer indicates that there is no such
2779  * article in this case.
2780  */
2781  data.data = NULL;
2782  data.size = 0;
2783  res = 0;
2784  }
2785  }
2786  if(check_connection(res)) { break; }
2787  }
2788  while(res && retries--);
2789  }
2790  data.result = res;
2791 }
2792 
2793 
2794 /* ========================================================================== */
2795 /* Get article body */
2796 
2797 static void get_article_body(void)
2798 {
2799  int res;
2800  size_t len = 0;
2801  char* body = NULL;
2802  unsigned int retries = CORE_NEXUS_RETRIES;
2803 
2804  do
2805  {
2806  res = nexus_handler(&n);
2807  if(!res)
2808  {
2809  res = nntp_get_article_body(n->nntp_handle,
2810  (const nntp_anum_t*) data.data,
2811  &body, &len);
2812  if(!res)
2813  {
2814  /* Store result */
2815  data.data = (void*) body;
2816  data.size = len;
2817  }
2818  }
2819  if(check_connection(res)) { break; }
2820  }
2821  while(res && retries--);
2822  data.result = res;
2823 }
2824 
2825 
2826 /* ========================================================================== */
2827 /* Post article */
2828 
2829 static void post_article(void)
2830 {
2831  int res;
2832  unsigned int retries = CORE_NEXUS_RETRIES;
2833 
2834  do
2835  {
2836  res = nexus_handler(&n);
2837  if(!res)
2838  {
2839  res = nntp_post_article(n->nntp_handle, (const char*) data.data);
2840  }
2841  if(check_connection(res)) { break; }
2842  }
2843  while(res && retries--);
2844  /* Release memory allocated for article by core */
2845  api_posix_free((void*) data.data);
2846  data.data = NULL;
2847  data.size = 0;
2848  data.result = res;
2849 }
2850 
2851 
2852 /* ========================================================================== */
2853 /* Core thread cleanup handler */
2854 
2855 static void cleanup_handler(void* arg)
2856 {
2857  (void) arg;
2858  /*
2859  * This function must not execute any potentially blocking I/O system calls
2860  * or other things that may not terminate. Otherwise cancelling the core
2861  * thread may fail and the program will freeze while joining the core thread.
2862  */
2863  if(main_debug)
2864  {
2865  printf("%s: %sExecute cleanup handler\n", CFG_NAME, MAIN_ERR_PREFIX);
2866  }
2867 }
2868 
2869 
2870 /* ========================================================================== */
2871 /* Core thread entry point */
2872 
2873 static void* core_main(void* arg)
2874 {
2875  int rv;
2876 
2877  (void) arg;
2878 
2879  /* Install cleanup handler */
2880  api_posix_pthread_cleanup_push(cleanup_handler, NULL);
2881 
2882  /* Condition handler */
2883  core_mutex_lock();
2884  while(1)
2885  {
2886  /* Wait for wakeup condition */
2887  rv = api_posix_pthread_cond_wait(&pt_cond, &pt_mutex);
2888  if(rv)
2889  {
2890  PRINT_ERROR("Waiting for condition failed");
2891  break;
2892  }
2893 
2894  /* Execute command */
2895  switch(command)
2896  {
2897  case CORE_CMD_GET_MOTD:
2898  {
2899  get_motd();
2900  break;
2901  }
2902  case CORE_CMD_GET_DISTRIB_PATS:
2903  {
2904  get_distrib_pats();
2905  break;
2906  }
2907  case CORE_CMD_GET_SUBSCRIPTIONS:
2908  {
2909  get_subscriptions();
2910  break;
2911  }
2912  case CORE_CMD_RESET_GROUPSTATES:
2913  {
2914  reset_group_states();
2915  break;
2916  }
2917  case CORE_CMD_GET_GROUPLIST:
2918  {
2919  get_group_list();
2920  break;
2921  }
2922  case CORE_CMD_GET_GROUPLABELS:
2923  {
2924  get_group_labels();
2925  break;
2926  }
2927  case CORE_CMD_GET_GROUPINFO:
2928  {
2929  get_groupinfo();
2930  break;
2931  }
2932  case CORE_CMD_SET_GROUP:
2933  {
2934  set_group();
2935  break;
2936  }
2937  case CORE_CMD_GET_OVERVIEW:
2938  {
2939  get_overview();
2940  break;
2941  }
2942  case CORE_CMD_GET_ARTICLE_BY_MID:
2943  {
2944  get_article_by_mid();
2945  break;
2946  }
2947  case CORE_CMD_GET_ARTICLE:
2948  {
2949  get_article();
2950  break;
2951  }
2952  case CORE_CMD_GET_ARTICLE_HEADER:
2953  {
2954  get_article_header();
2955  break;
2956  }
2957  case CORE_CMD_GET_ARTICLE_BODY:
2958  {
2959  get_article_body();
2960  break;
2961  }
2962  case CORE_CMD_POST_ARTICLE:
2963  {
2964  post_article();
2965  break;
2966  }
2967  /* This command is for internal use only */
2968  case CORE_TERMINATE_NEXUS:
2969  {
2970  nexus_close(&n);
2971  data.result = 0;
2972  break;
2973  }
2974  default:
2975  {
2976  PRINT_ERROR("Unknown command ignored");
2977  data.result = -1;
2978  break;
2979  }
2980  }
2981  /*
2982  * Note:
2983  * If 'data.result' indicates success, the UI must release the memory
2984  * allocated for 'data.data'.
2985  */
2986  command = CORE_CMD_INVALID;
2987 
2988  /* Wakeup UI thread to process the result */
2989  if(CORE_TERMINATE_NEXUS != command) { ui_wakeup(data.cookie); }
2990  }
2992 
2993  /* Remove cleanup handler */
2994  api_posix_pthread_cleanup_pop(1);
2995 
2996  return(NULL);
2997 }
2998 
2999 
3000 /* ========================================================================== */
3001 /* Wait for core thread command queue to become empty
3002  *
3003  * \param[in] checks Number of checks
3004  * \param[in] to Timeout in miliseconds
3005  */
3006 
3007 static int commands_in_queue(unsigned int checks, unsigned int to)
3008 {
3009  int res = -1;
3010  unsigned int i;
3011  int rv;
3012 
3013  /* Wait until nexus termination completes */
3014  for(i = 0; i < checks; ++i)
3015  {
3016  rv = time_msleep(to);
3017  if(rv) { break; }
3018  /* Check whether command queue is empty */
3019  rv = api_posix_pthread_mutex_trylock(&pt_mutex);
3020  if(!rv)
3021  {
3022  if(CORE_CMD_INVALID == command)
3023  {
3024  /* Yes => Return success */
3025  res = 0;
3026  }
3028  }
3029  if(!res) { break; }
3030  }
3031 
3032  return(res);
3033 }
3034 
3035 
3036 /* ========================================================================== */
3037 /* Destroy distribution pattern data object
3038  *
3039  * \param[out] pats Pointer to object
3040  */
3041 
3042 static void core_distrib_pats_destructor(struct distrib_pats*** pats)
3043 {
3044  size_t i = 0;
3045 
3046  if(NULL != pats && NULL != *pats)
3047  {
3048  while(NULL != (*pats)[i])
3049  {
3050  api_posix_free((void*) (*pats)[i]->wildmat);
3051  api_posix_free((void*) (*pats)[i]->dist);
3052  api_posix_free((void*) (*pats)[i++]);
3053  }
3054  api_posix_free((void*) *pats);
3055  *pats = NULL;
3056  }
3057 
3058  return;
3059 }
3060 
3061 
3062 /* ========================================================================== */
3063 /* Parse distribution patterns and create data object
3064  *
3065  * \param[out] pats Extracted patterns, weigths and distributions
3066  * \param[in] data Raw data to parse (must be a zero terminated string)
3067  *
3068  * The content of \e data is expected to be RFC 3977 conformant distribution
3069  * pattern information with lines in the following format:
3070  * <br>
3071  * weight:wildmat:distribution
3072  *
3073  * \note
3074  * If \e data is an empty list (this is allowed by RFC 3977), a valid empty
3075  * object is created and success is returned.
3076  *
3077  * On success a pointer to the result object is written to \e pats and the
3078  * caller is responsible to destroy the object with the function
3079  * \ref core_distrib_pats_destructor()
3080  */
3081 
3082 static int core_distrib_pats_constructor(struct distrib_pats*** pats,
3083  const char* data)
3084 {
3085  int res = -1;
3086  size_t sosp = sizeof(struct distrib_pats*);
3087  const char* raw = core_convert_canonical_to_posix(data, 0, 0);
3088  size_t i = 0;
3089  const char* p;
3090  size_t line_len;
3091  char* line = NULL;
3092  int rv;
3093  unsigned long int weight;
3094  char* wildmat = NULL;
3095  char* dist = NULL;
3096  struct distrib_pats* element = NULL;
3097  struct distrib_pats** object = NULL;
3098  size_t objects = 1;
3099  char* tmp;
3100  struct distrib_pats* tmp2;
3101  struct distrib_pats** tmp3;
3102 
3103  if(NULL != raw)
3104  {
3105  /* Check for empty list */
3106  if(!raw[i])
3107  {
3108  tmp3 = (struct distrib_pats**) api_posix_malloc(sosp);
3109  if(NULL != tmp3)
3110  {
3111  object = tmp3;
3112  object[0] = NULL;
3113  res = 0;
3114  }
3115  }
3116  else
3117  {
3118  /* Parse data */
3119  while(raw[i])
3120  {
3121  /* Parse next line */
3122  p = strchr(&raw[i], 0x0A);
3123  if(NULL == p)
3124  {
3125  /* Garbage at end of data => Ignore and treat as EOD */
3126  if(NULL != object) { res = 0; }
3127  break;
3128  }
3129  else
3130  {
3131  line_len = (size_t) (p - &raw[i]);
3132  if(line_len)
3133  {
3134  tmp = (char*) api_posix_realloc(line, line_len + (size_t) 1);
3135  if(NULL == tmp) { break; }
3136  else
3137  {
3138  line = tmp;
3139  strncpy(line, &raw[i], line_len);
3140  line[line_len] = 0;
3141  }
3142  tmp = (char*) api_posix_realloc(wildmat, line_len + (size_t) 1);
3143  if(NULL == tmp) { break; } else { wildmat = tmp; }
3144  tmp = (char*) api_posix_realloc(dist, line_len + (size_t) 1);
3145  if(NULL == tmp) { break; } else { dist = tmp; }
3146  rv = sscanf(line, "%lu:%[^][\\:\r\n]:%s", &weight, wildmat, dist);
3147  if(3 != rv)
3148  {
3149  PRINT_ERROR("Invalid distribution pattern ignored");
3150  }
3151  else
3152  {
3153  /* Create new element for object */
3154  tmp2 = (struct distrib_pats*)
3155  api_posix_malloc(sizeof(struct distrib_pats));
3156  if(NULL != tmp2)
3157  {
3158  element = tmp2;
3159  element->weight = weight;
3160  element->wildmat = wildmat; wildmat = NULL;
3161  element->dist = dist; dist = NULL;
3162  /* Add new element to object */
3163  tmp3 = (struct distrib_pats**)
3164  api_posix_realloc(object, ++objects * sosp);
3165  if(NULL == tmp3)
3166  {
3167  api_posix_free((void*) element->wildmat);
3168  api_posix_free((void*) element->dist);
3169  api_posix_free((void*) element);
3170  break;
3171  }
3172  else
3173  {
3174  object = tmp3;
3175  object[objects - 2] = element;
3176  object[objects - 1] = NULL;
3177  element = NULL;
3178  }
3179  }
3180  }
3181  }
3182  i += line_len;
3183  }
3184  ++i; /* Skip Linefeed */
3185  }
3186  /* Check for EOD */
3187  if(res && !raw[i] && NULL != object) { res = 0; }
3188  }
3189  }
3190  api_posix_free((void*) line);
3191  api_posix_free((void*) wildmat);
3192  api_posix_free((void*) dist);
3193  api_posix_free((void*) raw);
3194 
3195  /* Check for error */
3196  if(res) { core_distrib_pats_destructor(&object); }
3197  else { *pats = object; }
3198 
3199  return(res);
3200 }
3201 
3202 
3203 /* ========================================================================== */
3204 /* Check line length of article
3205  *
3206  * \param[in] article Article in canonical format (with CRLF line termination)
3207  *
3208  * Check for lines containing more than 998 octets.
3209  *
3210  * RFC 5322 (Mail) specifies a line length limit of 998 characters (excluding
3211  * the CRLF line termination). RFC 2045 (MIME) and RFC 5536 (Netnews) do not
3212  * relax this limit.
3213  *
3214  * A character is an US-ASCII codepoint in the sense of RFC 5322. This is
3215  * redefined by RFC 2045 to an octet and RFC 5536 is based on MIME.
3216  *
3217  * \return
3218  * - 0 on success
3219  * - -1 if lines with more than 998 octets were detected
3220  */
3221 
3222 static int core_check_line_length(const char* article)
3223 {
3224  int res = 0;
3225  size_t i = 0;
3226  size_t len;
3227  char* p;
3228 
3229  while(1)
3230  {
3231  /* Search for CR */
3232  p = strchr(&article[i], 0x0D);
3233  if(NULL == p) { break; }
3234  len = p - &article[i];
3235  /* Check for valid CRLF line break */
3236  if(0x0A != (int) article[i + len + (size_t) 1])
3237  {
3238  PRINT_ERROR("Invalid CR control character (not part of line break)");
3239  res = -1;
3240  break;
3241  }
3242  if (998 < len)
3243  {
3244  PRINT_ERROR("Article contains lines with more than 998 octets");
3245  res = -1;
3246  break;
3247  }
3248  i += len + (size_t) 1; /* Skip after CR */
3249  if(article[i]) { ++i; } /* Skip expected LF too */
3250  }
3251 
3252  /* Special handling for last line without CRLF */
3253  if(!res)
3254  {
3255  p = strchr(&article[i], 0x00);
3256  if(NULL == p)
3257  {
3258  PRINT_ERROR("End of string not found (bug)");
3259  res = -1;
3260  }
3261  else
3262  {
3263  len = p - &article[i];
3264  if (998 < len)
3265  {
3266  PRINT_ERROR("Article contains lines with more than 998 octets");
3267  res = -1;
3268  }
3269  }
3270  }
3271 
3272  return(res);
3273 }
3274 
3275 
3276 /* ========================================================================== */
3277 /*! \brief Extract groups from 'Newsgroups' header field (exported for UI)
3278  *
3279  * \param[in] body Unfolded body of \c Newsgroups header field
3280  *
3281  * Because the parameter \e body is allowed to have arbitrary size, the result
3282  * has arbitrary size too. The result array is terminated with a \c NULL entry.
3283  *
3284  * \note
3285  * The caller is responsible to free the memory for the array.
3286  *
3287  * \return
3288  * - Pointer to array of strings
3289  * - NULL on error
3290  */
3291 
3292 const char** core_extract_groups(const char* body)
3293 {
3294  return(extract_groups(body));
3295 }
3296 
3297 
3298 /* ========================================================================== */
3299 /*! \brief Get list of available newsgroups (exported for UI)
3300  *
3301  * \param[in] cookie Callback cookie
3302  *
3303  * The core will fetch the list with all groups that are available on the
3304  * server (always the default one from the configuration \ref config ).
3305  * After the operation was successfully started, the function returns (with the
3306  * value 1). After the operation has completed, the core thread calls the
3307  * function \ref ui_wakeup() with \e cookie as parameter.
3308  *
3309  * The UI can extract the result of the operation from the \ref core_data
3310  * object field \ref core_data::result (0 on success, negative on error).
3311  * On success the field \ref core_data::data points to a buffer with the array
3312  * of group descriptors.
3313  * The field \ref core_data::size contains the number of groups in the array.
3314  * If the server is available but doesn't contain groups, success is
3315  * returned with a \c NULL pointer and zero size.
3316  *
3317  * The caller is responsible to free the memory allocated for the array and all
3318  * of its elements.
3319  *
3320  * \return
3321  * - 1 indicates that the operation is in progress
3322  * - Negative value on error
3323  */
3324 
3325 int core_get_group_list(unsigned int cookie)
3326 {
3327  int res = -1;
3328  int rv = -1;
3329 
3330  /* Queue command */
3331  core_mutex_lock();
3332  if(CORE_CMD_INVALID == command)
3333  {
3334  data.cookie = cookie;
3335  data.result = -1;
3336  command = CORE_CMD_GET_GROUPLIST;
3337  res = 1;
3338 
3339  /* Wake up core thread */
3340  rv = api_posix_pthread_cond_signal(&pt_cond);
3341  if(rv) { PRINT_ERROR("Waking up core thread failed"); }
3342  }
3343  else
3344  {
3345  PRINT_ERROR("Command queue overflow");
3346  res = -1;
3347  }
3349 
3350  /* Check for error */
3351  if(rv) res = -1;
3352 
3353  return(res);
3354 }
3355 
3356 
3357 /* ========================================================================== */
3358 /*! \brief Get list of newsgroup labels (exported for UI)
3359  *
3360  * \param[in] cookie Callback cookie
3361  *
3362  * The core will fetch the list with newsgroup labels that are available on the
3363  * server (always the default one from the configuration \ref config ).
3364  * After the operation was successfully started, the function returns (with the
3365  * value 1). After the operation has completed, the core thread calls the
3366  * function \ref ui_wakeup() with \e cookie as parameter.
3367  *
3368  * The UI can extract the result of the operation from the \ref core_data
3369  * object field \ref core_data::result (0 on success, negative on error).
3370  * On success the field \ref core_data::data points to a buffer with the array
3371  * of group label structures.
3372  * The field \ref core_data::size contains the number of groups in the array.
3373  * If the server is available but doesn't contain groups, success is
3374  * returned with a \c NULL pointer and zero size.
3375  *
3376  * The caller is responsible to free the memory allocated for the array and all
3377  * of its elements.
3378  *
3379  * \return
3380  * - 1 indicates that the operation is in progress
3381  * - Negative value on error
3382  */
3383 
3384 int core_get_group_labels(unsigned int cookie)
3385 {
3386  int res = -1;
3387  int rv = -1;
3388 
3389  /* Queue command */
3390  core_mutex_lock();
3391  if(CORE_CMD_INVALID == command)
3392  {
3393  data.cookie = cookie;
3394  data.result = -1;
3395  command = CORE_CMD_GET_GROUPLABELS;
3396  res = 1;
3397 
3398  /* Wake up core thread */
3399  rv = api_posix_pthread_cond_signal(&pt_cond);
3400  if(rv) { PRINT_ERROR("Waking up core thread failed"); }
3401  }
3402  else
3403  {
3404  PRINT_ERROR("Command queue overflow");
3405  res = -1;
3406  }
3408 
3409  /* Check for error */
3410  if(rv) res = -1;
3411 
3412  return(res);
3413 }
3414 
3415 
3416 /* ========================================================================== */
3417 /*! \brief Alphabetically sort group list (exported for UI)
3418  *
3419  * \return
3420  * - 0 on success
3421  * - Negative value on error
3422  */
3423 
3425 {
3426  return(group_sort_list());
3427 }
3428 
3429 
3430 /* ========================================================================== */
3431 /*! \brief Store group subscription (exported for UI)
3432  *
3433  * \param[in] name Group name to add
3434  *
3435  * \note
3436  * The group \e name must have US-ASCII encoding or otherwise articles would
3437  * be not RFC 5536 conformant.
3438  *
3439  * \return
3440  * - 0 on success
3441  * - Negative value on error
3442  */
3443 
3444 int core_subscribe_group(const char* name)
3445 {
3446  int res = -1;
3447  struct core_groupstate gs;
3448 
3449  if(enc_ascii_check(name))
3450  {
3451  PRINT_ERROR("Subscription rejected for invalid group name");
3452  }
3453  else
3454  {
3455  gs.name = name;
3456  gs.info = NULL;
3457  res = group_add(&gs);
3458  }
3459 
3460  return(res);
3461 }
3462 
3463 
3464 /* ========================================================================== */
3465 /*! \brief Remove group from list (exported for UI)
3466  *
3467  * \param[in,out] num Pointer to number of groups in \e list
3468  * \param[in,out] list Pointer to array of group state structures
3469  * \param[in] index Index of group to unsubscribe in \e list
3470  *
3471  * \return
3472  * - 0 on success
3473  * - Negative value on error
3474  */
3475 
3476 int core_unsubscribe_group(size_t* num, struct core_groupstate** list,
3477  size_t* index)
3478 {
3479  int res = 0;
3480 
3481  if(!*num || *index > *num - (size_t) 1) { res = -1; }
3482  else
3483  {
3484  api_posix_free((void*) (*list)[*index].name);
3485  group_article_range_destructor(&(*list)[*index].info);
3486  if(*index < *num - (size_t) 1)
3487  {
3488  memmove((void*) &(*list)[*index],
3489  (void*) &(*list)[*index + (size_t) 1],
3490  sizeof(struct core_groupstate) * (*num - (size_t) 1 - *index));
3491  }
3492  --*num;
3493  if(*index) { --*index; }
3494  }
3495 
3496  return(res);
3497 }
3498 
3499 
3500 /* ========================================================================== */
3501 /*! \brief Reset states of subscribed groups (exported for UI)
3502  *
3503  * \param[in] cookie Callback cookie
3504  *
3505  * Closes the current server nexus and delete all group states.
3506  * This function must be called after the server has been changed and
3507  * the mapping from Message-IDs to article numbers is no longer valid.
3508  * After the operation was successfully started, the function returns (with the
3509  * value 1). After the operation has completed, the core thread calls the
3510  * function \ref ui_wakeup() with \e cookie as parameter.
3511  *
3512  * The UI can extract the result of the operation from the \ref core_data
3513  * object field \ref core_data::result (0 on success, negative on error).
3514  *
3515  * \return
3516  * - 1 indicates that the operation is in progress
3517  * - Negative value on error
3518  */
3519 
3520 int core_reset_group_states(unsigned int cookie)
3521 {
3522  int res = -1;
3523  int rv = -1;
3524 
3525  /* Queue command */
3526  core_mutex_lock();
3527  if(CORE_CMD_INVALID == command)
3528  {
3529  data.cookie = cookie;
3530  data.result = -1;
3531  command = CORE_CMD_RESET_GROUPSTATES;
3532  res = 1;
3533 
3534  /* Wake up core thread */
3535  rv = api_posix_pthread_cond_signal(&pt_cond);
3536  if(rv) { PRINT_ERROR("Waking up core thread failed"); }
3537  }
3538  else
3539  {
3540  PRINT_ERROR("Command queue overflow");
3541  res = -1;
3542  }
3544 
3545  /* Check for error */
3546  if(rv) res = -1;
3547 
3548  return(res);
3549 }
3550 
3551 
3552 /* ========================================================================== */
3553 /*! \brief Store states of subscribed groups (exported for UI)
3554  *
3555  * \param[in] num Pointer to number of groups
3556  * \param[in] list Pointer to array of group information structures
3557  *
3558  * \return
3559  * - 0 on success
3560  * - Negative value on error
3561  */
3562 
3563 int core_export_group_states(size_t num, struct core_groupstate* list)
3564 {
3565  return(group_set_list(num, list));
3566 }
3567 
3568 
3569 /* ========================================================================== */
3570 /*! \brief Get states of subscribed groups (exported for UI)
3571  *
3572  * The states which articles where already read are taken from the groupfile.
3573  * The last viewed articles of the groups are preserved.
3574  * The \e index is updated to match the new group list.
3575  *
3576  * \param[in,out] num Pointer to number of groups in \e list
3577  * \param[in,out] list Pointer to array of group state structures
3578  * \param[in,out] index Pointer to index of current group in \e list
3579  *
3580  * The caller is responsible to free the memory allocated for the information
3581  * object. The destructor function \ref core_destroy_subscribed_group_states()
3582  * should be used for this purpose.
3583  *
3584  * \return
3585  * - 0 on success
3586  * - Negative value on error
3587  */
3588 
3590  struct core_groupstate** list,
3591  size_t* index)
3592 {
3593  int res = -1;
3594  size_t num_old = *num;
3595  struct core_groupstate* list_old = *list;
3596  size_t index_old = *index;
3597  size_t i;
3598  size_t ii;
3599 
3600  /* Get new group list */
3601  res = group_get_list(num, list);
3602 
3603  /*
3604  * The current group database doesn't store the current index and the last
3605  * viewed article ID, therefore these fields are now invalid.
3606  */
3607 
3608  /* Reassign the missing values */
3609  if(num && num_old && NULL != *list && NULL != list_old)
3610  {
3611  *index = 0;
3612  for(i = 0; i < *num; ++i)
3613  {
3614  /* Process last viewed articles */
3615  for(ii = 0; ii < num_old; ++ii)
3616  {
3617  if(!strcmp((*list)[i].name, list_old[ii].name))
3618  {
3619  (*list)[i].last_viewed = list_old[ii].last_viewed;
3620  }
3621  }
3622  /* Process current index */
3623  if(!strcmp((*list)[i].name, list_old[index_old].name))
3624  {
3625  *index = index_old;
3626  }
3627  }
3628  }
3629 
3630  /* Free memory allocated for old list */
3631  group_destroy_list(&num_old, &list_old);
3632 
3633  return(res);
3634 }
3635 
3636 
3637 /* ========================================================================== */
3638 /*! \brief Destructor for subscribed group states (exported for UI)
3639  *
3640  * \param[in] num Pointer to number of groups
3641  * \param[in] list Pointer to array of group state structures
3642  *
3643  * This destructor is intended to destroy the object created by the function
3644  * \ref core_update_subscribed_group_states() .
3645  *
3646  * If \e list is \c NULL , the function do nothing.
3647  */
3648 
3650  struct core_groupstate** list)
3651 {
3652  group_destroy_list(num, list);
3653 }
3654 
3655 
3656 /* ========================================================================== */
3657 /*! \brief Get information about subscribed groups (exported for UI)
3658  *
3659  * \param[in] num Pointer to number of groups in \e list
3660  * \param[in] list Pointer to array of state structures
3661  * \param[in] cookie Callback cookie
3662  *
3663  * \note
3664  * An empty list with \e num pointing to zero is allowed.
3665  * The parameter \e list should point to \c NULL in this case.
3666  *
3667  * The core will collect the information for the groups listed in \e list from
3668  * the server (always the default one from the configuration
3669  * \ref config ).
3670  * After the operation was successfully started, the function returns (with the
3671  * value 1). After the operation has completed, the core thread calls the
3672  * function \ref ui_wakeup() with \e cookie as parameter.
3673  *
3674  * The UI can extract the result of the operation from the \ref core_data
3675  * object field \ref core_data::result (0 on success, negative on error).
3676  * On success the field \ref core_data::data points to an array of group
3677  * descriptors with \e num entries (or is \c NULL for an empty group list).
3678  *
3679  * The caller is responsible to free the memory allocated for the information
3680  * object. The destructor function \ref core_destroy_subscribed_group_info()
3681  * should be used for this purpose.
3682  *
3683  * \attention
3684  * Always call this function with all subscribed groups in \e list because the
3685  * article header cache is deleted for all groups not found in \e list as a side
3686  * effect.
3687  *
3688  * \return
3689  * - 1 indicates that the operation is in progress
3690  * - Negative value on error
3691  */
3692 
3693 int core_get_subscribed_group_info(const size_t* num,
3694  struct core_groupstate** list,
3695  unsigned int cookie)
3696 {
3697  int res = -1;
3698  int rv = -1;
3699 
3700  /* Queue command */
3701  core_mutex_lock();
3702  if(CORE_CMD_INVALID == command)
3703  {
3704  data.cookie = cookie;
3705  data.result = -1;
3706  data.size = *num;
3707  data.data = (void*) list;
3708  command = CORE_CMD_GET_GROUPINFO;
3709  res = 1;
3710 
3711  /* Wake up core thread */
3712  rv = api_posix_pthread_cond_signal(&pt_cond);
3713  if(rv) { PRINT_ERROR("Waking up core thread failed"); }
3714  }
3715  else
3716  {
3717  PRINT_ERROR("Command queue overflow");
3718  res = -1;
3719  }
3721 
3722  /* Check for error */
3723  if(rv) res = -1;
3724 
3725  return(res);
3726 }
3727 
3728 
3729 /* ========================================================================== */
3730 /*! \brief Destructor for subscribed group information (exported for UI)
3731  *
3732  * \param[in,out] list Pointer to array of group descriptors
3733  *
3734  * This destructor is intended to destroy the object created by the function
3735  * \ref core_get_subscribed_group_info() .
3736  *
3737  * If \e list is \c NULL , the function do nothing.
3738  */
3739 
3741 {
3742  api_posix_free((void*) *list);
3743  *list = NULL;
3744 }
3745 
3746 
3747 /* ========================================================================== */
3748 /*! \brief Set current group (exported for UI)
3749  *
3750  * \param[in] name Group name
3751  * \param[in] cookie Callback cookie
3752  *
3753  * The core will configure the server (always the default one from the
3754  * configuration \ref config ) to the current group \e name .
3755  * After the operation was successfully started, the function returns (with the
3756  * value 1). After the operation has completed, the core thread calls the
3757  * function \ref ui_wakeup() with \e cookie as parameter.
3758  *
3759  * The UI can extract the result of the operation from the \ref core_data
3760  * object field \ref core_data::result (0 on success, negative on error).
3761  * On success the field \ref core_data::data points to the buffer with the
3762  * group descriptor. The field \ref core_data::size is set to one.
3763  *
3764  * The caller is responsible to free the memory allocated for the buffer.
3765  *
3766  * \return
3767  * - 1 indicates that the operation is in progress
3768  * - Negative value on error
3769  */
3770 
3771 int core_set_group(const char* name, unsigned int cookie)
3772 {
3773  int res = -1;
3774  int rv = -1;
3775 
3776  /* Queue command */
3777  core_mutex_lock();
3778  if(CORE_CMD_INVALID == command)
3779  {
3780  data.cookie = cookie;
3781  data.result = -1;
3782  data.data = (void*) name;
3783  command = CORE_CMD_SET_GROUP;
3784  res = 1;
3785 
3786  /* Wake up core thread */
3787  rv = api_posix_pthread_cond_signal(&pt_cond);
3788  if(rv) { PRINT_ERROR("Waking up core thread failed"); }
3789  }
3790  else
3791  {
3792  PRINT_ERROR("Command queue overflow");
3793  res = -1;
3794  }
3796 
3797  /* Check for error */
3798  if(rv) res = -1;
3799 
3800  return(res);
3801 }
3802 
3803 
3804 /* ========================================================================== */
3805 /*! \brief Get message of the day (exported for UI)
3806  *
3807  * \param[in] cookie Callback cookie
3808  *
3809  * The core will fetch the message of the day from a server (currently
3810  * always the default one from the configuration \ref config ).
3811  *
3812  * After the operation was successfully started, the function returns (with the
3813  * value 1). After the operation has completed, the core thread calls the
3814  * function \ref ui_wakeup() with \e cookie as parameter.
3815  *
3816  * The UI can extract the result of the operation from the \ref core_data
3817  * object field \ref core_data::result (0 on success, negative on error).
3818  * On success the field \ref core_data::data points to the buffer with the
3819  * message of the day. The field \ref core_data::size contains the buffer size.
3820  * If the server is available, but doesn't provide a message of the day,
3821  * success is returned with a \c NULL pointer and zero size.
3822  *
3823  * The caller is responsible to free the memory allocated for the buffer.
3824  *
3825  * \return
3826  * - 1 indicates that the operation is in progress
3827  * - Negative value on error or if not supported
3828  */
3829 
3830 int core_get_motd(unsigned int cookie)
3831 {
3832  int res = -1;
3833  int rv = -1;
3834 
3835  if(NULL != n)
3836  {
3837  /* Check whether the server has message of the day capability */
3838  core_mutex_lock();
3839  if(!nntp_get_capa_list_motd(n->nntp_handle))
3840  {
3841  PRINT_ERROR("Server doesn't provide message of the day");
3842  }
3843  else
3844  {
3845  /* Yes => Queue command */
3846  if(CORE_CMD_INVALID == command)
3847  {
3848  data.cookie = cookie;
3849  data.result = -1;
3850  data.data = NULL;
3851  command = CORE_CMD_GET_MOTD;
3852  res = 1;
3853 
3854  /* Wake up core thread */
3855  rv = api_posix_pthread_cond_signal(&pt_cond);
3856  if(rv) { PRINT_ERROR("Waking up core thread failed"); }
3857  }
3858  else
3859  {
3860  PRINT_ERROR("Command queue overflow");
3861  res = -1;
3862  }
3863 
3864  /* Check for error */
3865  if(rv) res = -1;
3866  }
3868  }
3869 
3870  return(res);
3871 }
3872 
3873 
3874 /* ========================================================================== */
3875 /*! \brief Get distribution suggestions (exported for UI)
3876  *
3877  * \param[out] dist Suggested distribution for \e groups
3878  * \param[in] groups Newsgroup list (\c NULL for empty list is allowed)
3879  *
3880  * The core will fetch distribution suggestions (if available) from a server
3881  * (currently always the default one from the configuration \ref config )
3882  * for \e groups .
3883  * The result is the suggested content for the \c Distribution header field.
3884  *
3885  * On success, the caller is responsible to free the memory allocated for the
3886  * buffer.
3887  *
3888  * \return
3889  * - Zero on success (Pointer to new memory buffer was written to \e dist)
3890  * - Negative value for no suggestion and on error
3891  */
3892 
3893 int core_get_distribution(const char** dist, const char** groups)
3894 {
3895  int res = -1;
3896  const char* data;
3897  struct distrib_pats** pats = NULL;
3898 #if CFG_USE_POSIX_API >= 200112 || CFG_USE_XSI || CFG_USE_CLB
3899  int num;
3900  char* p;
3901  size_t len;
3902  size_t i = 0;
3903  size_t ii;
3904  int iii;
3905  char* buf = NULL;
3906  size_t buf_len = 0;
3907  int rv;
3908  struct enc_wm_pattern* wma = NULL;
3909  api_posix_regex_t ere;
3910  size_t weight;
3911  int match = -1;
3912 #endif /* CFG_USE_POSIX_API >= 200112 || CFG_USE_XSI || CFG_USE_CLB */
3913 
3914  core_mutex_lock();
3915  if(NULL == groups)
3916  {
3917  PRINT_ERROR("Empty group list for distribution suggestions");
3918  }
3919  else if(!config[CONF_DIST_SUGG].val.i)
3920  {
3921  PRINT_ERROR("Distribution suggestions disabled by configuration");
3922  }
3923  else
3924  {
3925  /* Get distribution patterns */
3926 #if 0
3927  /* For debugging */
3928  char x[1024];
3929  api_posix_snprintf(x, (size_t) 1024, "%s", "3:de.alt.test:de\r\n");
3930  data = x;
3931  res = 0;
3932 #else
3933  res = nntp_get_distrib_pats(n->nntp_handle, &data, NULL);
3934 #endif
3935  if(1 == res)
3936  {
3937  PRINT_ERROR("Server doesn't provide distribution suggestions");
3938  res = -1;
3939  }
3940  /* Parse distribution patterns */
3941  if(!res) { res = core_distrib_pats_constructor(&pats, data); }
3942  }
3944 
3945  /* Check groups against patterns */
3946  if(!res)
3947  {
3948 #if CFG_USE_POSIX_API >= 200112 || CFG_USE_XSI || CFG_USE_CLB
3949  res = -1;
3950  while(NULL != groups[i])
3951  {
3952  /*
3953  * Currently missing for Unicode groupname support:
3954  * - Groupname must be checked to be valid UTF-8
3955  * - Groupname must be normalized to NFC
3956  * - OS locale must use UTF-8 or groupname must be converted to the
3957  * character set of the locale
3958  */
3959  if(enc_ascii_check(groups[i]))
3960  {
3961  PRINT_ERROR("Unicode groupname ignored (not supported yet)");
3962  ++i;
3963  continue;
3964  }
3965  weight = 0;
3966  match = -1;
3967  ii = 0;
3968  while(NULL != pats[ii])
3969  {
3970  /*
3971  * Currently missing for Unicode wildmat support
3972  * - Wildmat must be checked to be valid UTF-8
3973  * - Wildmat must be normalized to NFC
3974  * - OS locale must use UTF-8 or wildmat must be converted to the
3975  * character set of the locale
3976  */
3977  if(enc_ascii_check(pats[ii]->wildmat))
3978  {
3979  PRINT_ERROR("Unicode wildmat ignored (not supported yet)");
3980  num = -1;
3981  }
3982  else
3983  {
3984  /* Convert wildmat to array of POSIX EREs */
3985  num = enc_create_wildmat(&wma, pats[ii]->wildmat);
3986  }
3987  if(0 < num)
3988  {
3989  /* Process array backwards to get rightmost pattern first */
3990  for(iii = num; iii; --iii)
3991  {
3992  /* Compile regular expression */
3993  rv = api_posix_regcomp(&ere, wma[iii - 1].ere,
3994  API_POSIX_REG_EXTENDED
3995  | API_POSIX_REG_NOSUB);
3996  if(rv)
3997  {
3998  PRINT_ERROR("Compiling regular expression failed");
3999  }
4000  else
4001  {
4002  /* Check whether group matches wildmat */
4003  rv = api_posix_regexec(&ere, groups[i], 0, NULL, 0);
4004  api_posix_regfree(&ere);
4005  if(!rv)
4006  {
4007  /* Yes */
4008  if(wma[iii - 1].negate)
4009  {
4010  }
4011  else
4012  {
4013  /* Check weigth */
4014  if(weight <= pats[ii]->weight)
4015  {
4016  /* Select this index as current best match */
4017  weight = pats[ii]->weight;
4018  match = (int) ii;
4019  }
4020  }
4021  break;
4022  }
4023  }
4024  }
4025  enc_destroy_wildmat(&wma, num);
4026  }
4027  /* Continue with next pattern */
4028  ++ii;
4029  }
4030  if(match >= 0)
4031  {
4032  /* Append suggested distribution */
4033  len = strlen(pats[match]->dist);
4034  if(!buf_len)
4035  {
4036  buf = (char*) api_posix_malloc(++len);
4037  if(NULL != buf)
4038  {
4039  buf_len = len;
4040  strcpy(buf, pats[match]->dist);
4041  *dist = buf;
4042  res = 0;
4043  }
4044  }
4045  else
4046  {
4047  p = (char*) api_posix_realloc((void*) buf, ++len + buf_len);
4048  if(NULL != p)
4049  {
4050  buf = p;
4051  buf_len += len;
4052  strcat(buf, ",");
4053  strcat(buf, pats[match]->dist);
4054  *dist = buf;
4055  }
4056  }
4057  }
4058  /* Continue with next group */
4059  ++i;
4060  }
4061 #else /* CFG_USE_POSIX_API >= 200112 || CFG_USE_XSI || CFG_USE_CLB */
4062  PRINT_ERROR("Distribution pattern matching requires regular "
4063  "expression support");
4064  res = -1;
4065 #endif /* CFG_USE_POSIX_API >= 200112 || CFG_USE_XSI || CFG_USE_CLB */
4066  }
4067 
4068  /* Destroy distribution pattern object */
4069  core_distrib_pats_destructor(&pats);
4070 
4071 #if 0
4072  /* For debugging */
4073  if(!res) { printf("Suggested distribution: %s\n", *dist); }
4074 #endif
4075 
4076  return(res);
4077 }
4078 
4079 
4080 /* ========================================================================== */
4081 /*! \brief Get subscription proposals (exported for UI)
4082  *
4083  * \param[in] cookie Callback cookie
4084  *
4085  * The core will fetch the subscription proposals from a server (currently
4086  * always the default one from the configuration \ref config ).
4087  *
4088  * \note
4089  * The data provided by this function is intended for initial population of
4090  * a newsrc file.
4091  *
4092  * After the operation was successfully started, the function returns (with the
4093  * value 1). After the operation has completed, the core thread calls the
4094  * function \ref ui_wakeup() with \e cookie as parameter.
4095  *
4096  * The UI can extract the result of the operation from the \ref core_data
4097  * object field \ref core_data::result (0 on success, negative on error).
4098  * On success the field \ref core_data::data points to the buffer with the
4099  * newsgroup list. The field \ref core_data::size contains the buffer size.
4100  * If the server is available, but doesn't provide a message of the day,
4101  * success is returned with a \c NULL pointer and zero size.
4102  *
4103  * The caller is responsible to free the memory allocated for the buffer.
4104  *
4105  * \return
4106  * - 1 indicates that the operation is in progress
4107  * - Negative value on error or if not supported
4108  */
4109 
4110 int core_get_subscription_proposals(unsigned int cookie)
4111 {
4112  int res = -1;
4113  int rv = -1;
4114 
4115  /* Queue command */
4116  if(CORE_CMD_INVALID == command)
4117  {
4118  data.cookie = cookie;
4119  data.result = -1;
4120  data.data = NULL;
4121  command = CORE_CMD_GET_SUBSCRIPTIONS;
4122  res = 1;
4123 
4124  /* Wake up core thread */
4125  rv = api_posix_pthread_cond_signal(&pt_cond);
4126  if(rv) { PRINT_ERROR("Waking up core thread failed"); }
4127  }
4128  else
4129  {
4130  PRINT_ERROR("Command queue overflow");
4131  res = -1;
4132  }
4134 
4135  /* Check for error */
4136  if(rv) res = -1;
4137 
4138  return(res);
4139 }
4140 
4141 
4142 /* ========================================================================== */
4143 /*! \brief Get article header overview (exported for UI)
4144  *
4145  * \param[in] range Pointer to article number range
4146  * \param[in] cookie Callback cookie
4147  *
4148  * The core will fetch the article headers of range \e range from a server
4149  * (currently always the default one from the configuration \ref config ).
4150  *
4151  * \attention
4152  * The \e range must be one contiguous range without holes. A list of ranges is
4153  * not allowed here.
4154  *
4155  * After the operation was successfully started, the function returns (with the
4156  * value 1). After the operation has completed, the core thread calls the
4157  * function \ref ui_wakeup() with \e cookie as parameter.
4158  *
4159  * The UI can extract the result of the operation from the \ref core_data
4160  * object field \ref core_data::result (0 on success, negative on error).
4161  * On success the field \ref core_data::data points to the buffer with the
4162  * article headers. The field \ref core_data::size contains the buffer size.
4163  * If the server is available but doesn't contain the requested
4164  * articles, success is returned with a \c NULL pointer and zero size.
4165  *
4166  * The caller is responsible to free the memory allocated for the buffer.
4167  *
4168  * \return
4169  * - 1 indicates that the operation is in progress
4170  * - Negative value on error or if not supported
4171  */
4172 
4173 int core_get_overview(struct core_range* range, unsigned int cookie)
4174 {
4175  int res = -1;
4176  int rv = -1;
4177 
4178  if(NULL != n)
4179  {
4180  /* Check whether server has overview capability */
4181  core_mutex_lock();
4182  if(nntp_get_capa_over(n->nntp_handle))
4183  {
4184  /* Yes => Queue command */
4185  if(CORE_CMD_INVALID == command)
4186  {
4187  data.cookie = cookie;
4188  data.result = -1;
4189  data.data = (void*) range;
4190  command = CORE_CMD_GET_OVERVIEW;
4191  res = 1;
4192 
4193  /* Wake up core thread */
4194  rv = api_posix_pthread_cond_signal(&pt_cond);
4195  if(rv) { PRINT_ERROR("Waking up core thread failed"); }
4196  }
4197  else
4198  {
4199  PRINT_ERROR("Command queue overflow");
4200  res = -1;
4201  }
4202 
4203  /* Check for error */
4204  if(rv) res = -1;
4205  }
4207  }
4208 
4209  return(res);
4210 }
4211 
4212 
4213 /* ========================================================================== */
4214 /*! \brief Get complete article by Message-ID (exported for UI)
4215  *
4216  * \param[in] mid Message-ID (with angle brackets)
4217  * \param[in] cookie Callback cookie
4218  *
4219  * The core will fetch the article with Message-ID \e mid from a server
4220  * (currently always the default one from the configuration \ref config ).
4221  * After the operation was successfully started, the function returns (with the
4222  * value 1). After the operation has completed, the core thread calls the
4223  * function \ref ui_wakeup() with \e cookie as parameter.
4224  *
4225  * \attention
4226  * \e mid must be valid until \ref ui_wakeup() is called.
4227  *
4228  * The UI can extract the result of the operation from the \ref core_data
4229  * object field \ref core_data::result (0 on success, negative on error).
4230  * On success the field \ref core_data::data points to the buffer with the
4231  * article. The field \ref core_data::size contains the buffer size.
4232  * If the server is available but doesn't contain the requested article,
4233  * success is returned with a \c NULL pointer and zero size.
4234  *
4235  * The caller is responsible to free the memory allocated for the buffer.
4236  *
4237  * \return
4238  * - 1 indicates that the operation is in progress
4239  * - Negative value on error
4240  */
4241 
4242 int core_get_article_by_mid(const char* mid, unsigned int cookie)
4243 {
4244  int res = -1;
4245  int rv = -1;
4246 
4247  /* Queue command */
4248  core_mutex_lock();
4249  if(CORE_CMD_INVALID == command)
4250  {
4251  data.cookie = cookie;
4252  data.result = -1;
4253  data.data = (void*) mid;
4254  command = CORE_CMD_GET_ARTICLE_BY_MID;
4255  res = 1;
4256 
4257  /* Wake up core thread */
4258  rv = api_posix_pthread_cond_signal(&pt_cond);
4259  if(rv) { PRINT_ERROR("Waking up core thread failed"); }
4260  }
4261  else
4262  {
4263  PRINT_ERROR("Command queue overflow");
4264  res = -1;
4265  }
4267 
4268  /* Check for error */
4269  if(rv) res = -1;
4270 
4271  return(res);
4272 }
4273 
4274 
4275 /* ========================================================================== */
4276 /*! \brief Get complete article (exported for UI)
4277  *
4278  * \param[in] id Article number
4279  * \param[in] cookie Callback cookie
4280  *
4281  * The core will fetch the article with number \e id from a server (currently
4282  * always the default one from the configuration \ref config ).
4283  * After the operation was successfully started, the function returns (with the
4284  * value 1). After the operation has completed, the core thread calls the
4285  * function \ref ui_wakeup() with \e cookie as parameter.
4286  *
4287  * \attention
4288  * \e id must be valid until \ref ui_wakeup() is called.
4289  *
4290  * The UI can extract the result of the operation from the \ref core_data
4291  * object field \ref core_data::result (0 on success, negative on error).
4292  * On success the field \ref core_data::data points to the buffer with the
4293  * article. The field \ref core_data::size contains the buffer size.
4294  * If the server is available but doesn't contain the requested article,
4295  * success is returned with a \c NULL pointer and zero size.
4296  *
4297  * The caller is responsible to free the memory allocated for the buffer.
4298  *
4299  * \return
4300  * - 1 indicates that the operation is in progress
4301  * - Negative value on error
4302  */
4303 
4304 int core_get_article(const core_anum_t* id, unsigned int cookie)
4305 {
4306  int res = -1;
4307  int rv = -1;
4308 
4309  /* Queue command */
4310  core_mutex_lock();
4311  if(CORE_CMD_INVALID == command)
4312  {
4313  data.cookie = cookie;
4314  data.result = -1;
4315  data.data = (void*) id;
4316  command = CORE_CMD_GET_ARTICLE;
4317  res = 1;
4318 
4319  /* Wake up core thread */
4320  rv = api_posix_pthread_cond_signal(&pt_cond);
4321  if(rv) { PRINT_ERROR("Waking up core thread failed"); }
4322  }
4323  else
4324  {
4325  PRINT_ERROR("Command queue overflow");
4326  res = -1;
4327  }
4329 
4330  /* Check for error */
4331  if(rv) res = -1;
4332 
4333  return(res);
4334 }
4335 
4336 
4337 /* ========================================================================== */
4338 /*! \brief Get article header (exported for UI)
4339  *
4340  * \param[in] id Article number
4341  * \param[in] cookie Callback cookie
4342  *
4343  * The core will fetch the article header with number \e id from a server
4344  * (currently always the default one from the configuration \ref config ).
4345  * After the operation was successfully started, the function returns (with the
4346  * value 1). After the operation has completed, the core thread calls the
4347  * function \ref ui_wakeup() with \e cookie as parameter.
4348  *
4349  * \attention
4350  * \e id must be valid until \ref ui_wakeup() is called.
4351  *
4352  * The UI can extract the result of the operation from the \ref core_data
4353  * object field \ref core_data::result (0 on success, negative on error).
4354  * On success the field \ref core_data::data points to the buffer with the
4355  * article header. The field \ref core_data::size contains the buffer size.
4356  * If the server is available but doesn't contain the requested article,
4357  * success is returned with a \c NULL pointer and zero size.
4358  *
4359  * The caller is responsible to free the memory allocated for the buffer.
4360  *
4361  * \return
4362  * - 1 indicates that the operation is in progress
4363  * - Negative value on error
4364  */
4365 
4366 int core_get_article_header(const core_anum_t* id, unsigned int cookie)
4367 {
4368  int res = -1;
4369  int rv = -1;
4370 
4371  /* Queue command */
4372  core_mutex_lock();
4373  if(CORE_CMD_INVALID == command)
4374  {
4375  data.cookie = cookie;
4376  data.result = -1;
4377  data.data = (void*) id;
4378  command = CORE_CMD_GET_ARTICLE_HEADER;
4379  res = 1;
4380 
4381  /* Wake up core thread */
4382  rv = api_posix_pthread_cond_signal(&pt_cond);
4383  if(rv) { PRINT_ERROR("Waking up core thread failed"); }
4384  }
4385  else
4386  {
4387  PRINT_ERROR("Command queue overflow");
4388  res = -1;
4389  }
4391 
4392  /* Check for error */
4393  if(rv) res = -1;
4394 
4395  return(res);
4396 }
4397 
4398 
4399 /* ========================================================================== */
4400 /*! \brief Get article body (exported for UI)
4401  *
4402  * \param[in] id Article number
4403  * \param[in] cookie Callback cookie
4404  *
4405  * The core will fetch the article body with number \e id from a server
4406  * (currently always the default one from the configuration \ref config ).
4407  * After the operation was successfully started, the function returns (with the
4408  * value 1). After the operation has completed, the core thread calls the
4409  * function \ref ui_wakeup() with \e cookie as parameter.
4410  *
4411  * \attention
4412  * \e id must be valid until \ref ui_wakeup() is called.
4413  *
4414  * The UI can extract the result of the operation from the \ref core_data object
4415  * field \ref core_data::result (0 on success, negative on error).
4416  * On success the field \ref core_data::data points to the buffer with the
4417  * article body. The field \ref core_data::size contains the buffer size.
4418  * If the server is available but doesn't contain the requested article,
4419  * success is returned with a \c NULL pointer and zero size.
4420  *
4421  * The caller is responsible to free the memory allocated for the buffer.
4422  *
4423  * \return
4424  * - 1 indicates that the operation is in progress
4425  * - Negative value on error
4426  */
4427 
4428 int core_get_article_body(const core_anum_t* id, unsigned int cookie)
4429 {
4430  int res = -1;
4431  int rv = -1;
4432 
4433  /* Queue command */
4434  core_mutex_lock();
4435  if(CORE_CMD_INVALID == command)
4436  {
4437  data.cookie = cookie;
4438  data.result = -1;
4439  data.data = (void*) id;
4440  command = CORE_CMD_GET_ARTICLE_BODY;
4441  res = 1;
4442 
4443  /* Wake up core thread */
4444  rv = api_posix_pthread_cond_signal(&pt_cond);
4445  if(rv) { PRINT_ERROR("Waking up core thread failed"); }
4446  }
4447  else
4448  {
4449  PRINT_ERROR("Command queue overflow");
4450  res = -1;
4451  }
4453 
4454  /* Check for error */
4455  if(rv) res = -1;
4456 
4457  return(res);
4458 }
4459 
4460 
4461 /* ========================================================================== */
4462 /*! \brief Post article (exported for UI)
4463  *
4464  * \param[in] article Pointer to Unicode article in RFC 5536 canonical form
4465  * \param[in] cookie Callback cookie
4466  *
4467  * This function will check whether the article contains non-ASCII characters.
4468  * If this is the case in the header, the article is rejected (this is
4469  * forbidden by RFC 5536).
4470  *
4471  * If the body contains non-ASCII characters, the body is converted to ISO 8859
4472  * if possible (this is recommended by RFC 2046, can be disabled with the
4473  * \c force_unicode option in configfile).
4474  * Two fields are appended to the header indicating a transfer encoding of
4475  * \c 8bit and a content type of \c text/plain. For the content type, a
4476  * parameter for the actual character set is appended. A second parameter is
4477  * appended that indicates \c fixed format (according to RFC 3676).
4478  *
4479  * If injection via external inews is configured, the result is never 1 and
4480  * success (or error) derived from the exit status is returned immediately.
4481  * The parameter \e cookie is not used in this case.
4482  *
4483  * Otherwise the core will post the article pointed to by \e article to the
4484  * server (currently always the default one from the configuration
4485  * \ref config ). After the operation was successfully started, the function
4486  * returns (with the value 1). After the operation has completed, the core
4487  * thread calls the function \ref ui_wakeup() with \e cookie as parameter.
4488  *
4489  * \note
4490  * The article is copied immediately, the data where \e article points to must
4491  * not be valid until the operation is complete.
4492  *
4493  * The UI can extract the result of the operation from the \ref core_data object
4494  * field \ref core_data::result (0 on success, negative on error).
4495  *
4496  * \return
4497  * - 0 indicates success (from injection delegation to external inews)
4498  * - 1 indicates that the operation is in progress
4499  * - Negative value on error
4500  */
4501 
4502 int core_post_article(const char* article, unsigned int cookie)
4503 {
4504  int res = 1;
4505  int rv;
4506  const char* a = NULL;
4507  const char* header = NULL;
4508  const char* body = NULL;
4509  char* p = NULL;
4510  char* q;
4511  const char* r;
4512  size_t len;
4513  const char hl_te[] = "Content-Transfer-Encoding: 8bit\r\n";
4514  const char hl_ct[] = "Content-Type: text/plain; charset=";
4515  const char hl_ct_ff[] = "; format=fixed\r\n";
4516  const char* cs_iana = "UTF-8";
4517 
4518  /* Check if there are non-ASCII characters used */
4519  if(!enc_ascii_check(article))
4520  {
4521  /* No => Send as is but allocate new memory block */
4522  len = strlen(article);
4523  p = (char*) api_posix_malloc(++len);
4524  if(NULL == p)
4525  {
4526  PRINT_ERROR("Memory allocation for article failed");
4527  res = -1;
4528  }
4529  else
4530  {
4531  memcpy(p, article, len);
4532  a = p;
4533  }
4534  }
4535  else
4536  {
4537  /* Yes => Check encoding and normalize Unicode to NFC */
4538  a = enc_convert_to_utf8_nfc(ENC_CS_UTF_8, article);
4539  if(NULL == a)
4540  {
4541  PRINT_ERROR("Article encoding check/normalization failed");
4542  res = -1;
4543  }
4544  else
4545  {
4546  /* Copy result into scope of local memory manager in any case */
4547  len = strlen(a);
4548  p = (char*) api_posix_malloc(++len);
4549  if(NULL == p)
4550  {
4551  PRINT_ERROR("Memory allocation for article failed");
4552  res = -1;
4553  }
4554  else { strcpy(p, a); }
4555  if(a != article) { enc_free((void*) a); }
4556  a = p;
4557  }
4558 
4559  /* Extract header (without the "empty line" separator) */
4560  if(0 <= res)
4561  {
4562  p = strstr(a, "\r\n\r\n");
4563  if(NULL == p)
4564  {
4565  PRINT_ERROR("Article to post contains no header separator");
4566  res = -1;
4567  }
4568  else
4569  {
4570  len = (size_t) (p - a) + (size_t) 2;
4571  q = (char*) api_posix_malloc(++len);
4572  if(NULL == q)
4573  {
4574  PRINT_ERROR("Memory allocation for article header failed");
4575  res = -1;
4576  }
4577  else
4578  {
4579  strncpy(q, a, --len); q[len] = 0;
4580  header = q;
4581  /* Verify that header is ASCII encoded */
4582  if(enc_ascii_check(header))
4583  {
4584  PRINT_ERROR("Article to post has invalid header encoding");
4585  res = -1;
4586  }
4587  }
4588  }
4589  }
4590 
4591  /* Extract body */
4592  if(0 <= res)
4593  {
4594  len = strlen(&p[4]);
4595  q = (char*) api_posix_malloc(++len);
4596  if(NULL == q)
4597  {
4598  PRINT_ERROR("Memory allocation for article body failed");
4599  res = -1;
4600  }
4601  else
4602  {
4603  memcpy(q, &p[4], --len); q[len] = 0;
4604  body = q;
4605  /* Check whether user has forced Unicode */
4606  if (!config[CONF_FORCE_UNICODE].val.i)
4607  {
4608  /* Convert body to target character set */
4609  r = enc_convert_to_8bit(NULL, body, &cs_iana);
4610  if(NULL != r)
4611  {
4612  /* 'cs_iana' now contains the new character set */
4613  if(r != body)
4614  {
4615  /* Copy result into scope of local memory manager */
4616  len = strlen(r);
4617  p = (char*) api_posix_malloc(++len);
4618  if(NULL == p)
4619  {
4620  PRINT_ERROR("Memory allocation for article body "
4621  "failed");
4622  res = -1;
4623  }
4624  else
4625  {
4626  memcpy(p, r, len);
4627  enc_free((void*) r);
4628  api_posix_free((void*) body);
4629  body = p;
4630  }
4631  }
4632  }
4633  }
4634  }
4635  }
4636 
4637  /* Add MIME transfer encoding and content type header fields */
4638  if(0 <= res)
4639  {
4640  len = strlen(header) + strlen(hl_te);
4641  len += strlen(hl_ct) + strlen(cs_iana) + strlen(hl_ct_ff);
4642  p = (char*) api_posix_malloc(++len);
4643  if(NULL == p)
4644  {
4645  PRINT_ERROR("Memory allocation for MIME header field failed");
4646  res = -1;
4647  }
4648  else
4649  {
4650  strcpy(p, header);
4651  strcat(p, hl_te);
4652  strcat(p, hl_ct); strcat(p, cs_iana); strcat(p, hl_ct_ff);
4653  api_posix_free((void*) header);
4654  header = p;
4655  }
4656  }
4657 
4658  /* Recombine header and body */
4659  if(0 <= res)
4660  {
4661  len = 2;
4662  len += strlen(header);
4663  len += strlen(body);
4664  p = (char*) api_posix_malloc(++len);
4665  if(NULL == p)
4666  {
4667  PRINT_ERROR("Memory allocation for final article failed");
4668  res = -1;
4669  }
4670  else
4671  {
4672  strcpy(p, header);
4673  strcat(p, "\r\n"); /* Empty line separator */
4674  strcat(p, body);
4675  api_posix_free((void*) a);
4676  a = p;
4677  }
4678  }
4679  }
4680 
4681  /* Release memory for article parts */
4682  api_posix_free((void*) header);
4683  api_posix_free((void*) body);
4684 
4685  /* Check line length */
4686  rv = core_check_line_length(a);
4687  if(rv) { res = -1; }
4688 
4689  /* Post article */
4690  if(1 == res)
4691  {
4692  /* Check whether external inews should be used */
4693  if(strlen(config[CONF_INEWS].val.s))
4694  {
4695  /* Yes => Delegate injection to external inews */
4696  res = ext_inews(a);
4697  if(res) { res = -1; }
4698  }
4699  else
4700  {
4701  /* No => Queue POST command for core thread */
4702  core_mutex_lock();
4703  if(CORE_CMD_INVALID == command)
4704  {
4705  data.cookie = cookie;
4706  data.result = -1;
4707  /* Core thread will release the memory block allocated for a */
4708  data.data = (void*) a;
4709  command = CORE_CMD_POST_ARTICLE;
4710 
4711  /* Wake up core thread */
4712  rv = api_posix_pthread_cond_signal(&pt_cond);
4713  if(rv)
4714  {
4715  PRINT_ERROR("Waking up core thread failed");
4716  /* Remove command from queue */
4717  command = CORE_CMD_INVALID;
4718  res = -1;
4719  }
4720  }
4721  else
4722  {
4723  PRINT_ERROR("Command queue overflow");
4724  res = -1;
4725  }
4727  }
4728  }
4729 
4730  /* Check for error */
4731  if(0 >= res) { api_posix_free((void*) a); }
4732 
4733  return(res);
4734 }
4735 
4736 
4737 /* ========================================================================== */
4738 /*! \brief Check whether article was alread read (exported for UI)
4739  *
4740  * \param[in] group Group in which article resides
4741  * \param[in] article Hierarchy element assigned to article
4742  *
4743  * \return
4744  * - 0 if article was not read yet
4745  * - Positive value if article was already read
4746  */
4747 
4749  struct core_hierarchy_element* article)
4750 {
4751  int res = 0;
4752  struct core_range* cr = group->info;
4753  core_anum_t a = article->anum;
4754 
4755  /* Search article in list of already read ranges */
4756  while(NULL != cr)
4757  {
4758  if(a >= cr->first && a <= cr->last) { res = 1; break; }
4759  cr = cr->next;
4760  }
4761 
4762  return(res);
4763 }
4764 
4765 
4766 /* ========================================================================== */
4767 /*! \brief Mark article as read (exported for UI)
4768  *
4769  * \param[in,out] group Group in which article resides
4770  * \param[in] article Article number
4771  */
4772 
4773 void core_mark_as_read(struct core_groupstate* group, core_anum_t article)
4774 {
4775  struct core_range* cr = group->info;
4776  core_anum_t a = article;
4777  int new_range = 0;
4778  int before_current = 0;
4779  int rv;
4780  struct core_range* nr;
4781  struct core_range tmp;
4782 
4783  /* Article number zero is reserved */
4784  if(a)
4785  {
4786 #if 0
4787  /* For debugging */
4788  printf("\nGroup: %s\n", group->name);
4789  while(NULL != cr)
4790  {
4791  printf(" %lu-%lu\n", cr->first, cr->last);
4792  cr = cr->next;
4793  }
4794  cr = group->info;
4795  printf("Mark as read: %lu\n", a);
4796 #endif
4797  /* Add article to list of already read ranges */
4798  if(NULL == cr) { new_range = 1; }
4799  else
4800  {
4801  while(1)
4802  {
4803  /* Check whether article is inside current range */
4804  if(a >= cr->first && a <= cr->last) { break; }
4805  /* Check whether article is before current range */
4806  if(a < cr->first)
4807  {
4808  /* Yes => Check whether current range can be extended downward */
4809  if(cr->first - (core_anum_t) 1 == a) { cr->first -= 1; }
4810  else { new_range = 1; before_current = 1; }
4811  break;
4812  }
4813  /* No => Check whether current range can be extended upward */
4814  if(cr->last + (core_anum_t) 1 == a) { cr->last += 1; break; }
4815  /* Check for last range */
4816  if(NULL == cr->next) { new_range = 1; break; }
4817  else { cr = cr->next; }
4818  }
4819  }
4820  if(new_range)
4821  {
4822  /* Insert new article range in list */
4823  rv = group_article_range_constructor(&nr, a, a, NULL);
4824  if(!rv)
4825  {
4826  if(NULL == cr) { group->info = cr = nr; }
4827  else
4828  {
4829  if(before_current)
4830  {
4831  /* Swapping new and current range */
4832  nr->next = cr->next;
4833  memcpy((void*) &tmp, (void*) cr, sizeof(struct core_range));
4834  memcpy((void*) cr, (void*) nr, sizeof(struct core_range));
4835  memcpy((void*) nr, (void*) &tmp, sizeof(struct core_range));
4836  }
4837  /* Add behind current range */
4838  nr->next = cr->next; cr->next = nr;
4839  }
4840  }
4841  }
4842  /* Merge adjacent ranges */
4843  cr = group->info;
4844  while(NULL != cr)
4845  {
4846  if(NULL != cr->next)
4847  {
4848  if(cr->last + (core_anum_t) 1 == cr->next->first)
4849  {
4850  cr->last = cr->next->last;
4851  nr = cr->next;
4852  cr->next = cr->next->next;
4853  api_posix_free((void*) nr);
4854  }
4855  }
4856  cr = cr->next;
4857  }
4858 #if 0
4859  /* For debugging */
4860  cr = group->info;
4861  while(NULL != cr)
4862  {
4863  printf(" %lu-%lu\n", cr->first, cr->last);
4864  cr = cr->next;
4865  }
4866 #endif
4867  }
4868 }
4869 
4870 
4871 /* ========================================================================== */
4872 /*! \brief Mark article as unread (exported for UI)
4873  *
4874  * \param[in,out] group Group in which article resides
4875  * \param[in] article Article number
4876  */
4877 
4879 {
4880  struct core_range* cr = group->info;
4881  core_anum_t a = article;
4882  struct core_range* lr = NULL;
4883  struct core_range* nr;
4884  struct core_range* tmp;
4885  int rv;
4886 
4887  /* Article number zero is reserved */
4888  if(a)
4889  {
4890 #if 0
4891  /* For debugging */
4892  printf("\nGroup: %s\n", group->name);
4893  while(NULL != cr)
4894  {
4895  printf(" %lu-%lu\n", cr->first, cr->last);
4896  cr = cr->next;
4897  }
4898  cr = group->info;
4899  printf("Mark as unread: %lu\n", a);
4900 #endif
4901  /* Remove article from list of already read ranges */
4902  while(NULL != cr)
4903  {
4904  if(a >= cr->first && a <= cr->last)
4905  {
4906  /* Article found in current range */
4907  if(cr->first == cr->last)
4908  {
4909  /* Remove article range */
4910  tmp = cr;
4911  cr = cr->next;
4912  tmp->next = NULL;
4914  if(NULL == lr) { group->info = cr; }
4915  else { lr->next = cr; }
4916  }
4917  else if(a == cr->first)
4918  {
4919  /* Modify start of article range */
4920  cr->first += (core_anum_t) 1;
4921  }
4922  else if(a == cr->last)
4923  {
4924  /* Modify end of article range */
4925  cr->last -= (core_anum_t) 1;
4926  }
4927  else
4928  {
4929  /* Split article range */
4930  rv = group_article_range_constructor(&nr, a + (core_anum_t) 1,
4931  cr->last, NULL);
4932  if(!rv)
4933  {
4934  cr->last = a - (core_anum_t) 1;
4935  nr->next = cr->next;
4936  cr->next = nr;
4937  }
4938  }
4939  break;
4940  }
4941  else { lr = cr; cr = cr->next; }
4942  }
4943 #if 0
4944  /* For debugging */
4945  cr = group->info;
4946  while(NULL != cr)
4947  {
4948  printf(" %lu-%lu\n", cr->first, cr->last);
4949  cr = cr->next;
4950  }
4951 #endif
4952  }
4953 }
4954 
4955 
4956 /* ========================================================================== */
4957 /*! \brief Convert from canonical (RFC 822) to local (POSIX) form
4958  *
4959  * According to RFC 822 and RFC 2049 this function accepts plain text article
4960  * content in canonical form and convert the CR+LF line breaks to local (POSIX,
4961  * single LF) form. Single CR and LF characters are preserved by default (this
4962  * can be overridden by \e rcr and \e rlf respectively).
4963  *
4964  * \param[in] s String to convert
4965  * \param[in] rcr Insert replacement character for single CR
4966  * \param[in] rlf Insert replacement character for single LF
4967  *
4968  * \return
4969  * - Pointer to decoded data (a new memory block was allocated)
4970  * - NULL on error
4971  */
4972 
4973 const char* core_convert_canonical_to_posix(const char* s, int rcr, int rlf)
4974 {
4975  return(enc_convert_canonical_to_posix(s, rcr, rlf));
4976 }
4977 
4978 
4979 /* ========================================================================== */
4980 /*! \brief Convert from local (POSIX) to canonical (RFC 822) form
4981  *
4982  * According to RFC 822 and RFC 2049 this function accepts plain text article
4983  * content in local (POSIX) form and convert the single LF line breaks to
4984  * canonical (CR+LF) form. Single CR characters are removed.
4985  *
4986  * \param[in] s String to convert
4987  *
4988  * \return
4989  * - Pointer to decoded data (a new memory block was allocated)
4990  * - NULL on error
4991  */
4992 
4993 const char* core_convert_posix_to_canonical(const char* s)
4994 {
4995  return(enc_convert_posix_to_canonical(s));
4996 }
4997 
4998 
4999 /* ========================================================================== */
5000 /*! \brief Manage article hierarchy in memory (exported for UI)
5001  *
5002  * \param[in,out] hier Pointer to article hierarchy pointer
5003  * \param[in] action What should be done
5004  * \param[in] anum Article number
5005  *
5006  * To use the default article hierarchy, \e hier shall be set to \c NULL .
5007  *
5008  * The core provides a facility to create a hierarchy from articles that contain
5009  * the header field "References". The UI can use it as follows:
5010  *
5011  * First the hierarchy must be initialized with the \e action
5012  * \ref CORE_HIERARCHY_INIT (this automatically destroys the old hierarchy
5013  * if one exist). All other parameters are ignored.
5014  *
5015  * At any time after initialization, the root node of the current hierarchy
5016  * can be read with the \e action \ref CORE_HIERARCHY_GETROOT . \e anum must be
5017  * zero and as additional parameter the address of a buffer must be
5018  * supplied to which the root node is written on success. The data type must be
5019  * \ref core_hierarchy_element \c ** .
5020  *
5021  * After an article header was fetched from the server, it can be added to the
5022  * hierarchy with the action \ref CORE_HIERARCHY_ADD . \e anum should be set to
5023  * the number that the server has assigned to the article and, as additional
5024  * parameter, a pointer to the raw article header must be provided. The data
5025  * type must be \c const \c char \c * . The hierarchy manager will parse the
5026  * article header, check it and extract all relevant information from it. With
5027  * this information an article header object is created that is part of the
5028  * hierarchy element object together with the article number, the parent and
5029  * child article pointers.
5030  *
5031  * The action \ref CORE_HIERARCHY_ADD_OVER does the same, except that a flag is
5032  * stored into the new node that indicates the incomplete data. This action is
5033  * intended to add NNTP overview data.
5034  *
5035  * \attention
5036  * For superseding to work, articles must be added in the correct order.
5037  * In other words: The article that should be superseded must already be part of
5038  * the hierarchy. In general this can easily achieved by adding the articles by
5039  * number in ascending order.
5040  *
5041  * The resulting hierarchy is a tree object that the UI can display without
5042  * detailed knowledge of the article header format.
5043  *
5044  * In some cases the data in the hierarchy elements is incomplete, e.g. if the
5045  * hierarchy was created from NNTP overview data. It is possible to recreate
5046  * single hierarchy elements with additional data in place, e.g. after the
5047  * complete article was downloaded later. To update an existing hierarchy
5048  * element, the action must be set to \ref CORE_HIERARCHY_UPDATE . The
5049  * parameters are the same as for \ref CORE_HIERARCHY_ADD .
5050  *
5051  * \return
5052  * - 0 on success
5053  * - Negative value on error
5054  */
5055 
5057  enum core_hierarchy_action action,
5058  core_anum_t anum, ...)
5059 {
5060  va_list ap; /* Object for argument list handling */
5061  int res = 0;
5062 
5063  va_start(ap, anum);
5064 
5065  if(NULL == hier) { hier = &h; }
5066 
5067  switch(action)
5068  {
5069  case CORE_HIERARCHY_INIT:
5070  {
5071  res = hierarchy_init(hier);
5072  break;
5073  }
5075  {
5076  if(NULL == *hier) { res = -1; }
5077  else { *va_arg(ap, struct core_hierarchy_element**) = *hier; }
5078  break;
5079  }
5080  case CORE_HIERARCHY_ADD:
5081  {
5082  res = hierarchy_add(hier, anum, 0U, va_arg(ap, const char*));
5083  break;
5084  }
5086  {
5087  res = hierarchy_add(hier, anum, CORE_HE_FLAG_OVER,
5088  va_arg(ap, const char*));
5089  break;
5090  }
5091  case CORE_HIERARCHY_UPDATE:
5092  {
5093  res = hierarchy_update(hier, anum, va_arg(ap, const char*));
5094  break;
5095  }
5096  default:
5097  {
5098  PRINT_ERROR("Invalid article hierarchy action");
5099  break;
5100  }
5101  }
5102 
5103  va_end(ap);
5104 
5105  return(res);
5106 }
5107 
5108 
5109 /* ========================================================================== */
5110 /*! \brief Create article hierarchy from header overview (exported for UI)
5111  *
5112  * Parser for RFC 3977 conformant header overview data as provided by the
5113  * function \ref core_get_overview() .
5114  *
5115  * \note
5116  * All articles in \e range that are missing in \e overview are marked as read.
5117  *
5118  * \param[in,out] group Group in which articles reside
5119  * \param[in] range Pointer to article number range
5120  * \param[in] overview Pointer to article header overview data
5121  */
5122 
5124  struct core_range* range,
5125  const char* overview)
5126 {
5127  static const char mime_v_field[] = "MIME-Version: 1.0\r\n";
5128  static const char newsgroups_field[] = "Newsgroups: ";
5129  /* Names of corresponding header fields for overview data fields */
5130  static const char* field[] =
5131  {
5132  "", "Subject: ", "From: ", "Date: ", "Message-ID: ", "References: ",
5133  "", "Lines: "
5134  };
5135  size_t i = 0; /* Index in overview */
5136  size_t len;
5137  char* content; /* Content of single field */
5138  char* header = NULL; /* Pseudo-header generated from overview data */
5139  size_t hi; /* Index in pseudo-header */
5140  size_t hlen = 4096;
5141  int rv;
5142  core_anum_t ai = 0;
5143  core_anum_t aic = range->first;
5144  const char* p;
5145  char* q;
5146  unsigned int f;
5147  unsigned int f_max = 7; /* There are 7 mandatory fields */
5148  size_t tmp;
5149  int error = 0;
5150 
5151  /* Get index of optional Newsgroups header field in overview */
5152  core_mutex_lock();
5153  rv = nntp_get_over_newsgroups_index(n->nntp_handle, &tmp);
5155  if(!rv && (size_t) 7 < tmp && API_POSIX_UINT_MAX > tmp)
5156  {
5157  /* Overview does contain Newsgroups header field */
5158  f_max = tmp;
5159  }
5160 
5161  /* Allocate initial buffer for pseudo-header */
5162  header = (char*) api_posix_malloc(hlen);
5163  /* Allocate buffer for 'sscanf()' that is guaranteed large enough */
5164  len = strlen(overview);
5165  content = (char*) api_posix_malloc(++len);
5166  /* Process overview lines */
5167  while(NULL != header && NULL != content)
5168  {
5169  /* Prepend MIME version header field (for header parser) */
5170  strcpy(header, mime_v_field);
5171  hi = sizeof(mime_v_field) - (size_t) 1; /* Subtract 1 for NUL */
5172  /* Insert current group for FILTER module (if not in overview) */
5173  if(7 == f_max)
5174  {
5175  rv = api_posix_snprintf(&header[hi], hlen - hi, "%s%s\r\n",
5176  newsgroups_field, group->name);
5177  if(0 < rv)
5178  {
5179  if(hlen - hi > (size_t) rv) { hi += (size_t) rv; }
5180  }
5181  }
5182  /* Extract article watermark */
5183  rv = sscanf(&overview[i], "%s", content);
5184  if(1 == rv)
5185  {
5186  rv = enc_convert_ascii_to_anum(&ai, content, (int) strlen(content));
5187  if(!rv && ai)
5188  {
5189  /* Mark missing articles as read */
5190  while(aic < ai) { core_mark_as_read(group, aic++); }
5191  ++aic;
5192  /* Extract other fields */
5193  for(f = 1; f_max >= f; ++f)
5194  {
5195  /* Search for start of next field (start with index 1) */
5196  p = strchr(&overview[i], 0x09);
5197  if(NULL == p)
5198  {
5199  /* Field missing in overview */
5200  error = 1;
5201  break;
5202  }
5203  i += (size_t) (p - &overview[i]) + (size_t) 1;
5204  /* Skip unused fields */
5205  if(6U == f || (7U < f && f_max != f)) { continue; }
5206  /* Extract next field (allowed to be empty!) */
5207  if(0x09 == (int) (unsigned char) overview[i]) { continue; }
5208  rv = sscanf(&overview[i], "%[^\t]", content);
5209  if(1 == rv)
5210  {
5211  /*
5212  * Allocate more memory if required
5213  * (3 additional bytes for termination "\r\n\0")
5214  */
5215  len = strlen(field[f]) + strlen(content) + (size_t) 3;
5216  while(hlen - hi < len)
5217  {
5218  q = (char*) api_posix_realloc(header, hlen *= (size_t) 2);
5219  if(NULL == q) { error = 1; break; }
5220  else { header = q; }
5221  }
5222  /* Append new header field to pseudo-header */
5223  if(7 >= f)
5224  {
5225  rv = api_posix_snprintf(&header[hi], hlen - hi, "%s%s\r\n",
5226  field[f], content);
5227  }
5228  else
5229  {
5230  /* Optional header fields must have a ":full" tag */
5231  rv = api_posix_snprintf(&header[hi], hlen - hi, "%s\r\n",
5232  content);
5233  }
5234  if(0 < rv)
5235  {
5236  if(hlen - hi <= (size_t) rv)
5237  {
5238  PRINT_ERROR("Buffer overflow in header overview"
5239  " parser detected (Bug)");
5240  error = 1;
5241  break;
5242 
5243  }
5244  hi += (size_t) rv;
5245  }
5246  }
5247  }
5248  if(!error)
5249  {
5250 #if 0
5251  /* For debugging */
5252  printf("Pseudo-Header generated from overview:\n---\n%s---\n",
5253  header);
5254 #endif
5256  ai, header);
5257  if(0 > rv) { error = 1; }
5258  }
5259  if(error)
5260  {
5261  PRINT_ERROR("Header overview parser failed");
5262  break;
5263  }
5264  }
5265  }
5266  /* Skip to next line */
5267  p = strchr(&overview[i], 0x0A);
5268  if(NULL == p) { break; } /* End of overview data */
5269  i += (size_t) (p - &overview[i]) + (size_t) 1;
5270  }
5271 
5272  /* Mark potential missing articles at end of range as read */
5273  while(range->last > ai) { core_mark_as_read(group, ++ai); }
5274 
5275  /* Check for error and free memory */
5276  if(NULL == header || NULL == content)
5277  {
5278  PRINT_ERROR("Out of memory while processing header overview data");
5279  }
5280  api_posix_free((void*) header);
5281  api_posix_free((void*) content);
5282 }
5283 
5284 
5285 /* ========================================================================== */
5286 /*! \brief Parse header of MIME multipart entity (exported for UI)
5287  *
5288  * \param[in] entity Pointer to beginning of MIME multipart entity
5289  * \param[in] len Length of MIME multipart entity
5290  * \param[out] e_h Object with decoded header of MIME multipart entity
5291  * \param[out] e_len Length of MIME multipart entity content
5292  *
5293  * On success the caller is responsible to free the memory allocated for the
5294  * decoded header object. Use the function \ref core_destroy_entity_header()
5295  * to do this.
5296  *
5297  * \return
5298  * - Pointer to beginning of MIME multipart entity content on success
5299  * - NULL on error
5300  */
5301 
5302 const char* core_entity_parser(const char* entity, size_t len,
5303  struct core_article_header** e_h,
5304  size_t* e_len)
5305 {
5306  const char* res = NULL;
5307  char* h = NULL;
5308  size_t h_len;
5309  size_t sob = 0; /* Start of body */
5310  size_t i;
5311  int rv;
5312 
5313  /* Split entity into header and body */
5314  for(i = 0; i < len; ++i)
5315  {
5316  if(i && (char) 0x0A == entity[i])
5317  {
5318  if((char) 0x0D == entity[i - (size_t) 1])
5319  {
5320  /* End of line found => Check for empty line */
5321  if((size_t) 1 == i) { sob = ++i; break; }
5322  else if((size_t) 3 <= i)
5323  {
5324  if((char) 0x0A == entity[i - (size_t) 2]
5325  && (char) 0x0D == entity[i - (size_t) 3])
5326  {
5327  sob = ++i;
5328  break;
5329  }
5330  }
5331  }
5332  }
5333  }
5334  if((size_t) 2 <= sob)
5335  {
5336  *e_len = len - sob;
5337  /* Strip the empty line between header and body */
5338  h_len = sob - (size_t) 2;
5339  h = (char*) api_posix_malloc(h_len + (size_t) 1);
5340  if(NULL != h)
5341  {
5342  /* Copy header and store pointer to start of body */
5343  strncpy(h, entity, h_len); h[h_len] = 0;
5344  res = &entity[sob];
5345  }
5346  }
5347 
5348  /* Parse header of entity */
5349  if(NULL != h)
5350  {
5351  rv = header_object_constructor(e_h, h);
5352  if(rv) { res = NULL; }
5353  }
5354  api_posix_free((void*) h);
5355 
5356  return(res);
5357 }
5358 
5359 
5360 /* ========================================================================== */
5361 /*! \brief Destructor for MIME multipart entity header object (exported for UI)
5362  *
5363  * \param[in,out] ehp Pointer to decoded header object of MIME multipart entity
5364  */
5365 
5367 {
5368  header_object_destructor(ehp);
5369 }
5370 
5371 
5372 /* ========================================================================== */
5373 /*! \brief Get home directory of user (exported for UI)
5374  *
5375  * \note
5376  * On success, the caller is responsible to free the memory allocated for the
5377  * result.
5378  *
5379  * \return
5380  * - Pointer to result on success
5381  * - \c NULL on error
5382  */
5383 
5384 const char* core_get_homedir(void)
5385 {
5386  const char* buf = NULL;
5387 
5388  if(!ts_getenv("HOME", &buf))
5389  {
5390  if(fu_check_path(buf))
5391  {
5392  api_posix_free((void*) buf);
5393  buf = NULL;
5394  }
5395  }
5396 
5397  return(buf);
5398 }
5399 
5400 
5401 /* ========================================================================== */
5402 /*! \brief Suggest pathname to save something to a file (exported for UI)
5403  *
5404  * Intended as suggestion for "save as" file selection dialogs.
5405  *
5406  * This implementation uses the program name and the date to create the
5407  * filename. The users home directory is used as path.
5408  *
5409  * \note
5410  * On success, the caller is responsible to free the memory allocated for the
5411  * result.
5412  *
5413  * \return
5414  * - Pointer to result on success
5415  * - \c NULL on error
5416  */
5417 
5418 const char* core_suggest_pathname(void)
5419 {
5420  char* buf = NULL;
5421  const char* path = core_get_homedir();
5422  const char* name = CFG_NAME;
5423  char date[17] = { 0 };
5424  api_posix_time_t ts;
5425  api_posix_struct_tm t_data;
5426  api_posix_struct_tm* t = NULL;
5427  size_t len = 1; /* NUL termination */
5428  int rv;
5429 
5430  if(NULL != path)
5431  {
5432  api_posix_time(&ts);
5433  t = api_posix_gmtime_r(&ts, &t_data);
5434  if(NULL != t)
5435  {
5436  /* Create ISO 8601 timestamp */
5437  rv = api_posix_snprintf(date, (size_t) 17,
5438  "%04d%02d%02dT%02d%02d%02dZ",
5439  t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
5440  t->tm_hour, t->tm_min, t->tm_sec);
5441  if(16 != rv)
5442  {
5443  PRINT_ERROR("Created date and time string is invalid (bug)");
5444  }
5445  else
5446  {
5447  len += strlen(path); /* Path to home directory */
5448  len += 1; /* Slash */
5449  len += strlen(name); /* Program name */
5450  len += 1; /* Separator */
5451  len += strlen(date); /* Timestamp */
5452  buf = (char*) api_posix_malloc(len);
5453  if(NULL != buf)
5454  {
5455  strcpy(buf, path);
5456  strcat(buf, "/");
5457  strcat(buf, name);
5458  strcat(buf, "-");
5459  strcat(buf, date);
5460  }
5461  }
5462  }
5463  }
5464 
5465  return(buf);
5466 }
5467 
5468 
5469 /* ========================================================================== */
5470 /*! \brief Get current date and time in RFC 5322 format (exported for UI)
5471  *
5472  * \param[in] force_utc Force usage of UTC time with unknown timezone
5473  *
5474  * RFC 5322 recommends but not require to use the local time. If the
5475  * POSIX.1-2001 API is available and \e force_utc is set to zero, then local
5476  * time is used. Otherwise the returned date is UTC and marked with the unknown
5477  * timezone information \c -0000 defined by RFC 5322.
5478  *
5479  * If \c timestamp_comment option is enabled in configfile and \e force_utc is
5480  * set to a nonzero value, a comment with the abbreviation of the timezone is
5481  * appended (if the POSIX.1-2001 API or the X/Open XSI extension are provided
5482  * by the operating system).
5483  *
5484  * \note
5485  * On success, the caller is responsible to free the memory allocated for the
5486  * result.
5487  *
5488  * \return
5489  * - Pointer to result on success
5490  * - \c NULL on error
5491  */
5492 
5493 const char* core_get_datetime(int force_utc)
5494 {
5495  static const char* months[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
5496  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
5497  static const char* weekday[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri",
5498  "Sat"};
5499  char* buf = NULL;
5500  size_t len = 1; /* NUL termination */
5501  api_posix_time_t ts;
5502  api_posix_struct_tm t_data;
5503  api_posix_struct_tm* t = NULL;
5504  char t_zone[6] = { 0, 0, 0, 0, 0, 0 };
5505  int error = 0;
5506  int fallback = 1;
5507  int rv;
5508 
5509  /* Check whether user has configured to hide local time */
5510  if(!config[CONF_TS_LTIME].val.i) { force_utc = 1; }
5511 
5512  /* Calculate required buffer size */
5513  len += 31; /* RFC 5322 date */
5514  len += 8; /* Timezone: SP + '(' + 5 digits + ')' */
5515  /* Allocate buffer for result */
5516  buf = (char*) api_posix_malloc(len);
5517  if(NULL != buf)
5518  {
5519  api_posix_time(&ts);
5520 #if CFG_USE_POSIX_API >= 200112
5521  if(!force_utc)
5522  {
5523  t = api_posix_localtime_r(&ts, &t_data);
5524  if(NULL != t)
5525  {
5526  /* Get timezone information ("%z" is not available with SUSv2) */
5527  if(api_posix_strftime(t_zone, 6, "%z", t)) { fallback = 0; }
5528  }
5529  }
5530 #endif /* CFG_USE_POSIX_API >= 200112 */
5531  if(fallback)
5532  {
5533  /* Fallback for backward compatibility: No timezone information */
5534  t = api_posix_gmtime_r(&ts, &t_data);
5535  if(NULL == t) { error = 1; }
5536  else { strcpy(t_zone, "-0000"); }
5537  }
5538  if(!error)
5539  {
5540  rv = api_posix_snprintf(buf, len, "%s, %d %s %04d %02d:%02d:%02d %s",
5541  weekday[t->tm_wday],
5542  t->tm_mday, months[t->tm_mon],
5543  t->tm_year + 1900,
5544  t->tm_hour, t->tm_min, t->tm_sec, t_zone);
5545  if(!(30 <= rv && 31 >= rv))
5546  {
5547  PRINT_ERROR("Created date and time string is invalid (bug)");
5548  error = 1;
5549  }
5550  }
5551 
5552 #if CFG_USE_XSI || CFG_USE_POSIX_API >= 200112
5553  if(config[CONF_TS_COMMENT].val.i)
5554  {
5555  /* Add comment with timezone name */
5556  if(!fallback)
5557  {
5558  /* Accept only names that are not longer than numerical format */
5559  if(!api_posix_strftime(t_zone, 6, "%Z", t))
5560  {
5561  PRINT_ERROR("Creating timezone name comment failed");
5562  }
5563  else
5564  {
5565  strcat(buf, " (");
5566  strcat(buf, t_zone);
5567  strcat(buf, ")");
5568  }
5569  }
5570  }
5571 #endif /* CFG_USE_XSI || CFG_USE_POSIX_API >= 200112 */
5572  }
5573 
5574  /* Check for error */
5575  if(error)
5576  {
5577  api_posix_free((void*) buf);
5578  buf = NULL;
5579  }
5580 
5581  return(buf);
5582 }
5583 
5584 
5585 /* ========================================================================== */
5586 /*! \brief Get globally unique Message-ID (exported for UI)
5587  *
5588  * \param[in] fqdn Fully qualified domain name without root domain
5589  *
5590  * \attention
5591  * \e fqdn must be specified without the (nameless) DNS root domain, this means
5592  * there must be no trailing dot! \e fqdn must match the \c dot-atom syntax
5593  * defined in RFC 5322 to be usable as \c id-right element of a Message-ID.
5594  *
5595  * According to RFC 5536 the following rules are applied:
5596  * - The Message-ID must not be more than 250 octets in length => We check this.
5597  * - The Message-ID must be globally unique for all time => We use algorithm A3.
5598  * - The \c id-right element should be a domain => We use FQDN w/o root domain.
5599  * - Message-IDs should be unpredictable => We use a random field.
5600  *
5601  * Description of Message-ID format created by algorithm A3:
5602  * <br>
5603  * \c date \c random_pid . \c A3 . \c program \@ \c fqdn
5604  *
5605  * The date field contains a modified base64 encoded POSIX timestamp (seconds
5606  * since epoche) with 48bits.
5607  *
5608  * The random and pid fields are combined into a modified base64 encoded value
5609  * with 48bits. The first 16bits are random, they are added to deal with real
5610  * world clocks that are often not synchronized with UTC and don't always run
5611  * monotonic. They also make the Message-ID unpredictable as recommended by
5612  * RFC 5536. The last 32bits are the 32 LSBs of the process identifier (PID).
5613  * The function \c getpid() is used for this purpose. Both parts are combined
5614  * to one value to make the length an integral multiple of octet triplets that
5615  * can be base64 encoded without padding.
5616  *
5617  * The modified base64 encoding uses the base64 alphabet with \c / (slash)
5618  * replaced by \c - (minus).
5619  *
5620  * The Ax field represents the algorithm used to create the message ID. If the
5621  * algorithm is modified in the future, the number x should be incremented.
5622  *
5623  * The program field contains the value of the variable \c CFG_NAME from the
5624  * configuration file.
5625  *
5626  * The fqdn field is taken from the parameter \e fqdn and is checked for valid
5627  * \c dot-atom syntax according to RFC 5322.
5628  *
5629  * \note
5630  * On success, the caller is responsible to free the memory allocated for the
5631  * result.
5632  *
5633  * \return
5634  * - Pointer to result on success
5635  * - \c NULL on error
5636  */
5637 
5638 const char* core_get_msgid(const char* fqdn)
5639 {
5640  const char datext[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
5641  "0123456789" "!#$%&'*+-/=?^_`{|}~" ".";
5642  char* buf = NULL;
5643  size_t len = 0;
5644  api_posix_time_t ts;
5645  core_time_t ts_int;
5646  unsigned long int r;
5647  unsigned long int pid;
5648  int rv;
5649  int error = 0;
5650  unsigned char secs[6];
5651  unsigned char rpp[6];
5652  const char* buf_date = NULL;
5653  const char* buf_rpp = NULL;
5654  size_t i;
5655 
5656  /* Check "fqdn" parameter */
5657  if(NULL == fqdn) { error = 1; }
5658  else
5659  {
5660  len = strlen(fqdn);
5661  if(!len) { error = 1; }
5662  /* Check for dot-atom syntax */
5663  else if( len != strspn(fqdn, datext)
5664  || '.' == fqdn[0]
5665  || '.' == fqdn[len - (size_t) 1] )
5666  {
5667  PRINT_ERROR("Invalid FQDN format (dot-atom syntax required)");
5668  error = 1;
5669  }
5670  }
5671  if(!error)
5672  {
5673  len = 1; /* NUL termination */
5674  /* Calculate required buffer size */
5675  ++len; /* Opening angle bracket */
5676  len += (size_t) 8; /* Date */
5677  len += (size_t) 8; /* Random+PID */
5678  ++len; /* Dot */
5679  len += (size_t) 2; /* Algorithm */
5680  ++len; /* Dot */
5681  len += strlen(CFG_NAME); /* Program name */
5682  ++len; /* Commercial at */
5683  len += strlen(fqdn); /* FQDN (without root domain) */
5684  ++len; /* Closing angle bracket */
5685  /* Verify length of Message-ID */
5686  if((size_t) 251 < len)
5687  {
5688  PRINT_ERROR("Invalid message ID (FQDN too long)");
5689  error = 1;
5690  }
5691  }
5692  if(!error)
5693  {
5694  /* Prepare date field */
5695  api_posix_time(&ts);
5696  ts_int = (core_time_t) ts; /* ts is allowed to be floating point */
5697  secs[0] = 0;
5698  secs[1] = 0;
5699  secs[2] = (unsigned char) (ts_int >> 24);
5700  secs[3] = (unsigned char) (ts_int >> 16);
5701  secs[4] = (unsigned char) (ts_int >> 8);
5702  secs[5] = (unsigned char) (ts_int & 0xFFU);
5703  rv = enc_mime_encode_base64(&buf_date, (char*) secs, 6);
5704  if(rv) { error = 1; }
5705  }
5706  if(!error)
5707  {
5708  /* Prepare random+pid field */
5709  r = (unsigned long int) api_posix_random();
5710  rpp[0] = (unsigned char) (r >> 8);
5711  rpp[1] = (unsigned char) (r & 0xFFU);
5712  pid = (unsigned long int) api_posix_getpid();
5713  rpp[2] = (unsigned char) (pid >> 24);
5714  rpp[3] = (unsigned char) (pid >> 16);
5715  rpp[4] = (unsigned char) (pid >> 8);
5716  rpp[5] = (unsigned char) (pid & 0xFFU);
5717  rv = enc_mime_encode_base64(&buf_rpp, (char*) rpp, 6);
5718  if(rv) { error = 1; }
5719  }
5720  if(!error)
5721  {
5722  /* Allocate buffer for result */
5723  buf = (char*) api_posix_malloc(len);
5724  if(NULL != buf)
5725  {
5726  /* Create message ID */
5727  strcpy(buf, "<");
5728  strcat(buf, buf_date);
5729  strcat(buf, buf_rpp);
5730  strcat(buf, ".A3." CFG_NAME "@");
5731  strcat(buf, fqdn);
5732  strcat(buf, ">");
5733  }
5734  }
5735  /* Release memory allocated by base64 encoder */
5736  enc_free((void*) buf_date);
5737  enc_free((void*) buf_rpp);
5738  /* Replace "/" with "-" in <id-left> */
5739  for(i = 0; len > i; ++i)
5740  {
5741  if('@' == buf[i]) { break; }
5742  if('/' == buf[i]) { buf[i] = '-'; }
5743  }
5744 
5745  if(main_debug && NULL == buf)
5746  {
5747  fprintf(stderr, "%s: %sFunction core_get_msgid() returned error\n",
5748  CFG_NAME, MAIN_ERR_PREFIX);
5749  }
5750 
5751  return(buf);
5752 }
5753 
5754 
5755 /* ========================================================================== */
5756 /*! \brief Create Cancel-Key for Message-ID (exported for UI)
5757  *
5758  * \param[in] scheme Algorithm to use for "scheme"
5759  * \param[in] msgid Message-ID of article that should correspond to Cancel-Key
5760  *
5761  * This function creates a \c c-key element with with \e scheme and \e mid
5762  * according to RFC 8315.
5763  *
5764  * On success, the caller is responsible for releasing the memory allocated for
5765  * the result.
5766  *
5767  * \return
5768  * - Pointer to the buffer containing the created \c c-key element on success
5769  * - \c NULL on error
5770  */
5771 
5772 const char* core_get_cancel_key(unsigned int scheme, const char* msgid)
5773 {
5774  char* res = NULL;
5775  const char* confdir = NULL;
5776  const char* spn = NULL; /* Secret file pathname */
5777  int fd = -1;
5778  const char* scheme_string = NULL;
5779  const char* sec = NULL; /* Pointer to deprecated secret in config array */
5780  char secret[64]; /* Buffer for secret from separate file */
5781  size_t mac_len = 0;
5782  unsigned char mac[HMAC_SHA2_256_LEN]; /* Large enough for all schemes */
5783  const char* mac_enc;
5784  size_t len;
5785  int rv;
5786 
5787  switch(scheme)
5788  {
5789  case CORE_CL_SHA1:
5790  {
5791  mac_len = HMAC_SHA1_160_LEN;
5792  scheme_string = "sha1:";
5793  break;
5794  }
5795  case CORE_CL_SHA256:
5796  {
5797  mac_len = HMAC_SHA2_256_LEN;
5798  scheme_string = "sha256:";
5799  break;
5800  }
5801  default:
5802  {
5803  PRINT_ERROR("Scheme requested for Cancel-Key not supported");
5804  break;
5805  }
5806  }
5807 
5808  if(NULL != scheme_string)
5809  {
5810  rv = -1;
5811  switch(scheme)
5812  {
5813  case CORE_CL_SHA1:
5814  {
5815  /* Use secret from configfile for backward compatibility */
5816  if (strlen(config[CONF_CANCELKEY].val.s))
5817  {
5818  sec = config[CONF_CANCELKEY].val.s;
5819  }
5820  if(NULL != sec)
5821  {
5822  rv = hmac_sha1_160(msgid, strlen(msgid), sec, strlen(sec), mac);
5823  /* Do not overwrite secret in config array */
5824  }
5825  break;
5826  }
5827  case CORE_CL_SHA256:
5828  {
5829  /* Load secret from separate file */
5830  confdir = xdg_get_confdir(CFG_NAME);
5831  if(NULL != confdir)
5832  {
5833  rv = fu_create_path(confdir,
5834  (api_posix_mode_t) API_POSIX_S_IRWXU);
5835  if(0 == rv)
5836  {
5837  spn = confdir;
5839  if(0 == rv)
5840  {
5841  if(main_debug)
5842  {
5843  printf("%s: %sCL secret file: %s\n",
5844  CFG_NAME, MAIN_ERR_PREFIX, spn);
5845  }
5846  /* Generate new secret, if file does not already exist */
5847  secure_cl_secret(spn);
5848  rv = fu_open_file(spn, &fd, API_POSIX_O_RDONLY, 0);
5849  api_posix_free((void*) spn); spn = NULL;
5850  if(rv)
5851  {
5852  PRINT_ERROR("Opening CL secret file failed");
5853  }
5854  else
5855  {
5856  len = 64;
5857  rv = fu_read_from_filedesc(fd, secret, &len);
5858  fu_close_file(&fd, NULL);
5859  if(rv)
5860  {
5861  PRINT_ERROR("No secret available for CL scheme "
5862  "SHA256");
5863  }
5864  else
5865  {
5866  if(main_debug)
5867  {
5868  printf("%s: %sSize of CL secret: %u"
5869  " octets\n", CFG_NAME, MAIN_ERR_PREFIX,
5870  (unsigned int) len);
5871  }
5872  if((size_t) 32 > len)
5873  {
5874  PRINT_ERROR("Warning: Secret for CL scheme "
5875  "SHA256 too short");
5876  }
5877  rv = hmac_sha2_256(msgid, strlen(msgid),
5878  secret, len, mac);
5879  /* Overwrite secret in memory */
5880  secure_clear_memory(secret, len);
5881  }
5882  }
5883  }
5884  }
5885  }
5886  break;
5887  }
5888  default:
5889  {
5890  PRINT_ERROR("Invalid Cancel-Key scheme (bug)");
5891  break;
5892  }
5893  }
5894 
5895  if(!rv)
5896  {
5897  rv = enc_mime_encode_base64(&mac_enc, (char*) mac, mac_len);
5898  if(!rv)
5899  {
5900  len = strlen(scheme_string);
5901  len += strlen(mac_enc);
5902  res = (char*) api_posix_malloc(++len);
5903  if(NULL != res)
5904  {
5905  strcpy(res, scheme_string);
5906  strcat(res, mac_enc);
5907  }
5908  enc_free((void*) mac_enc);
5909  }
5910  }
5911  }
5912 
5913  return(res);
5914 }
5915 
5916 
5917 /* ========================================================================== */
5918 /*! \brief Create Cancel-Lock for Message-ID (exported for UI)
5919  *
5920  * \param[in] scheme Algorithm to use for "scheme"
5921  * \param[in] mid Message-ID of article that should correspond to Cancel-Lock
5922  *
5923  * This function creates a \c c-lock element with with \e scheme and \e mid
5924  * according to RFC 8315.
5925  *
5926  * On success, the caller is responsible for releasing the memory allocated for
5927  * the result.
5928  *
5929  * \return
5930  * - Pointer to the buffer containing the created \c c-lock element on success
5931  * - \c NULL on error
5932  */
5933 
5934 const char* core_get_cancel_lock(unsigned int scheme, const char* mid)
5935 {
5936  char* res = NULL;
5937  const char* scheme_string = NULL;
5938  const char* ckey = NULL;
5939  const char* key = NULL;
5940  size_t md_len = 0;
5941  unsigned char md[DIGEST_SHA2_256_LEN]; /* Large enough for all schemes */
5942  const char* md_enc;
5943  size_t len;
5944  int rv;
5945 
5946  switch(scheme)
5947  {
5948  case CORE_CL_SHA1:
5949  {
5950  md_len = DIGEST_SHA1_160_LEN;
5951  scheme_string = "sha1:";
5952  break;
5953  }
5954  case CORE_CL_SHA256:
5955  {
5956  md_len = DIGEST_SHA2_256_LEN;
5957  scheme_string = "sha256:";
5958  break;
5959  }
5960  default:
5961  {
5962  PRINT_ERROR("Scheme requested for Cancel-Lock not supported");
5963  break;
5964  }
5965  }
5966 
5967  if(NULL != scheme_string)
5968  {
5969  ckey = core_get_cancel_key(scheme, mid);
5970  if(NULL != ckey)
5971  {
5972  len = strlen(scheme_string);
5973  /* Check and strip scheme */
5974  if(strlen(ckey) < len || strncmp(ckey, scheme_string, len))
5975  {
5976  PRINT_ERROR("Cancel-Key has unsupported scheme (bug)");
5977  }
5978  else { key = &ckey[len]; }
5979  if(NULL != key)
5980  {
5981  rv = -1;
5982  switch(scheme)
5983  {
5984  case CORE_CL_SHA1:
5985  {
5986  rv = digest_sha1_160(key, strlen(key), md);
5987  break;
5988  }
5989  case CORE_CL_SHA256:
5990  {
5991  rv = digest_sha2_256(key, strlen(key), md);
5992  break;
5993  }
5994  default:
5995  {
5996  PRINT_ERROR("Invalid Cancel-Lock scheme (bug)");
5997  break;
5998  }
5999  }
6000  if(!rv)
6001  {
6002  rv = enc_mime_encode_base64(&md_enc, (char*) md, md_len);
6003  if(!rv)
6004  {
6005  len += strlen(md_enc);
6006  res = (char*) api_posix_malloc(++len);
6007  if(NULL != res)
6008  {
6009  strcpy(res, scheme_string);
6010  strcat(res, md_enc);
6011  }
6012  enc_free((void*) md_enc);
6013  }
6014  }
6015  }
6016  }
6017  }
6018  api_posix_free((void*) ckey);
6019 
6020  return(res);
6021 }
6022 
6023 
6024 /* ========================================================================== */
6025 /*! \brief Get signature for outgoing messages (exported for UI)
6026  *
6027  * \param[out] warnings Pointer to location for result warning flags
6028  *
6029  * Use the \c CORE_SIG_FLAG_xxx constants to decode \e warnings .
6030  *
6031  * \note
6032  * It is allowed to pass \c NULL for \e warnings if the caller is not interested
6033  * in this data.
6034  *
6035  * \note
6036  * On success, the caller is responsible to free the memory allocated for the
6037  * result.
6038  *
6039  * \return
6040  * - Pointer to result on success
6041  * - \c NULL on error (nothing was written to \e warnings )
6042  */
6043 
6044 const char* core_get_signature(unsigned int* warnings)
6045 {
6046  char* res = NULL;
6047  const char* homedir = NULL;
6048  const char sigdir[] = "/";
6049  char* sigpathname = NULL;
6050  const char* sigfile = NULL;
6051  int rv;
6052  int fd;
6053  size_t len;
6054  unsigned int warn_flags = 0;
6055  size_t i;
6056  size_t lines = 0;
6057 
6058  /* Get home directory from environment */
6059  rv = ts_getenv("HOME", &homedir);
6060  /* Prepare configuration directory */
6061  if(!rv)
6062  {
6063  /* Check home directory path */
6064  rv = fu_check_path(homedir);
6065  /* Create signature file pathname */
6066  if(!rv)
6067  {
6068  /* Take signature file from configuration */
6069  if(strlen(config[CONF_SIGFILE].val.s))
6070  {
6071  sigfile = config[CONF_SIGFILE].val.s;
6072  }
6073  if(NULL != sigfile)
6074  {
6075  /* The additional byte is for the terminating NUL */
6076  sigpathname = (char*) api_posix_malloc(strlen(homedir)
6077  + strlen(sigdir)
6078  + strlen(sigfile)
6079  + (size_t) 1);
6080  if(NULL == sigpathname)
6081  {
6082  PRINT_ERROR(""
6083  "Cannot allocate memory for config file pathname");
6084  }
6085  else
6086  {
6087  strcpy(sigpathname, homedir);
6088  strcat(sigpathname, sigdir);
6089  strcat(sigpathname, sigfile);
6090  /* printf("Signature file: %s\n", sigpathname); */
6091  /* Check whether file exist */
6092  rv = fu_check_file(sigpathname, NULL);
6093  if(!rv)
6094  {
6095  /* Open signature file and copy content to memory buffer */
6096  rv = fu_open_file(sigpathname, &fd, API_POSIX_O_RDONLY, 0);
6097  if(!rv)
6098  {
6099  rv = fu_read_whole_file(fd, &res, &len);
6100  if(rv) { res = NULL; }
6101  fu_close_file(&fd, NULL);
6102  }
6103  }
6104  }
6105  }
6106  }
6107  api_posix_free((void*) sigpathname);
6108  api_posix_free((void*) homedir);
6109  }
6110  else { PRINT_ERROR("Environment variable 'HOME' is not defined"); }
6111 
6112  /* Return warning flags on success */
6113  if(NULL != res)
6114  {
6115  /* Check for missing separator */
6116  if(strncmp(res, "-- \n", (size_t) 4))
6117  {
6118  warn_flags |= CORE_SIG_FLAG_SEPARATOR;
6119  }
6120  /* This warning means that the signature is not valid UTF-8 */
6121  if(enc_uc_check_utf8(res)) { warn_flags |= CORE_SIG_FLAG_INVALID; }
6122  /* This warning means that the signature is not US-ASCII */
6123  if(enc_ascii_check(res)) { warn_flags |= CORE_SIG_FLAG_NOTASCII; }
6124  /* This warning means that the signature contains more than 4 lines */
6125  len = strlen(res);
6126  if(len) { lines = 1; }
6127  for(i = 0; i < len; ++i)
6128  {
6129  if(0x0A == res[i])
6130  {
6131  if(i + (size_t) 1 != len)
6132  {
6133  if(API_POSIX_SIZE_MAX > lines) { ++lines; }
6134  }
6135  }
6136  }
6137  if(lines && !(warn_flags & CORE_SIG_FLAG_SEPARATOR)) { --lines; }
6138  if((size_t) 4 < lines) { warn_flags |= CORE_SIG_FLAG_LENGTH; }
6139  *warnings = warn_flags;
6140  }
6141 
6142  return(res);
6143 }
6144 
6145 
6146 /* ========================================================================== */
6147 /*! \brief Get introduction line for citation (exported for UI)
6148  *
6149  * \param[in] ca Name of cited author
6150  * \param[in] ngl Newsgroup list of the cited article
6151  *
6152  * The result of this function is an introduction line string without linebreak
6153  * at the end. The format is taken from the configuration \ref config .
6154  *
6155  * \note
6156  * On success, the caller is responsible to free the memory allocated for the
6157  * result.
6158  *
6159  * \return
6160  * - Pointer to result on success
6161  * - \c NULL on error
6162  */
6163 
6164 const char* core_get_introduction(const char* ca, const char* ngl)
6165 {
6166  char* res = NULL;
6167  int error = 0;
6168  const char* cfg;
6169  char* fmt = NULL;
6170  char* p = NULL;
6171  const char* c1 = NULL;
6172  const char* c2 = NULL;
6173  int len;
6174 
6175  if(NULL != ca)
6176  {
6177  res = (char*) api_posix_malloc((size_t) 998);
6178  if(NULL != res)
6179  {
6180  /* Check format from configuration */
6181  cfg = config[CONF_INTRO].val.s;
6182  fmt = (char*) api_posix_malloc(strlen(cfg) + (size_t) 1);
6183  if (NULL == fmt) { error = 1; }
6184  else
6185  {
6186  strcpy(fmt, cfg);
6187  p = strchr(fmt, (int) '%');
6188  if(NULL != p)
6189  {
6190  /* Check first conversion */
6191  if('s' == p[1]) { c1 = ca; }
6192  else if('g' == p[1]) { p[1] = 's'; c1 = ngl; }
6193  else { error = 2; }
6194  if(!error)
6195  {
6196  p = strchr(&p[2], (int) '%');
6197  if(NULL != p)
6198  {
6199  /* Check second conversion */
6200  if('s' == p[1]) { c2 = ca; }
6201  else if('g' == p[1]) { p[1] = 's'; c2 = ngl; }
6202  else { error = 2; }
6203  if(!error)
6204  {
6205  if(NULL != strchr(&p[2], (int) '%'))
6206  {
6207  fprintf(stderr, "%s: %s"
6208  "More than 2 conversions in introduction line "
6209  "are not supported\n", CFG_NAME, MAIN_ERR_PREFIX);
6210  error = 1;
6211  }
6212  }
6213  if(2 == error)
6214  {
6215  fprintf(stderr, "%s: %s"
6216  "Conversion type in introduction line not "
6217  "supported\n", CFG_NAME, MAIN_ERR_PREFIX);
6218  }
6219  }
6220  }
6221  }
6222  if(!error)
6223  {
6224  /* Create introduction line */
6225  len = api_posix_snprintf(res, 998, fmt, c1, c2);
6226  if(0 >= len) { error = 1; }
6227  }
6228  api_posix_free((void*) fmt);
6229  }
6230  /* Check for error */
6231  if(error)
6232  {
6233  api_posix_free((void*) res);
6234  res = NULL;
6235  }
6236  }
6237  }
6238 
6239  return(res);
6240 }
6241 
6242 
6243 /* ========================================================================== */
6244 /*! \brief Convert pathname to codeset of locale (exported for UI)
6245  *
6246  * \param[in] pathname Pathname to convert (UTF-8 encoded)
6247  *
6248  * On success the caller is responsible to free the memory allocated for the
6249  * returned pathname.
6250  *
6251  * \todo
6252  * As target this function uses the codeset that was detected for the locale
6253  * category \c LC_CTYPE by the \c FILTER module.
6254  * The locale parsing code should be moved to the \c CORE module.
6255  *
6256  * \return
6257  * - Pointer to (potentially converted) pathname on success
6258  * - NULL on error
6259  */
6260 
6261 const char* core_convert_pathname_to_locale(const char* pathname)
6262 {
6263  char* res;
6264  int error = 0;
6265  size_t len = strlen(pathname) + (size_t) 1;
6266  const char* rv;
6267  const char* rv2;
6268  enum enc_mime_cs cs;
6269 
6270  /* Attention: The conversions must never increase the size of the string! */
6271  res = (char*) api_posix_malloc(len);
6272  if(NULL != res)
6273  {
6274  switch(filter_get_locale_ctype())
6275  {
6276  case FILTER_CS_UTF_8:
6277  {
6278  /* Check and normalize to NFC */
6279  rv = enc_convert_to_utf8_nfc(ENC_CS_UTF_8, pathname);
6280  if(NULL == rv) { error = 1; }
6281  else
6282  {
6283  if(len <= strlen(rv)) { error = 1; }
6284  else { strcpy(res, rv); }
6285  if(rv != pathname) { enc_free((void*) rv); }
6286  }
6287  break;
6288  }
6289  case FILTER_CS_ISO8859_1:
6290  {
6291  /* Check and normalize to NFC */
6292  rv = enc_convert_to_utf8_nfc(ENC_CS_UTF_8, pathname);
6293  if(NULL == rv) { error = 1; }
6294  else
6295  {
6296  /* Convert to ISO 8859-1 */
6297  rv2 = enc_convert_to_8bit(&cs, rv, NULL);
6298  if(NULL == rv2) { error = 1; }
6299  else
6300  {
6301  if(ENC_CS_ISO8859_1 != cs || len <= strlen(rv2))
6302  {
6303  error = 1;
6304  }
6305  else { strcpy(res, rv2); }
6306  if(rv2 != rv) { enc_free((void*) rv2); }
6307  }
6308  if(rv != pathname) { enc_free((void*) rv); }
6309  }
6310  break;
6311  }
6312  case FILTER_CS_ASCII:
6313  {
6314  if(enc_ascii_check(pathname)) { error = 1; }
6315  else { strcpy(res, pathname); }
6316  break;
6317  }
6318  default:
6319  {
6320  PRINT_ERROR("Unknown codeset (bug)");
6321  error = 1;
6322  break;
6323  }
6324  }
6325  }
6326 
6327  /* Check for error */
6328  if(error)
6329  {
6330  PRINT_ERROR("Pathname conversion to codeset of locale failed");
6331  api_posix_free((void*) res);
6332  res = NULL;
6333  }
6334 
6335  return(res);
6336 }
6337 
6338 
6339 /* ========================================================================== */
6340 /*! \brief Check wheter file exists (exported for UI)
6341  *
6342  * \param[in] pathname Pathname of file (UTF-8 encoded)
6343  *
6344  * \note
6345  * The encoding of the pathname is converted to the encoding of the locale.
6346  *
6347  * \return
6348  * - 0 on success (file exists)
6349  * - Negative value on error
6350  */
6351 
6352 int core_check_file_exist(const char* pathname)
6353 {
6354  int res = -1;
6355  const char* pn = core_convert_pathname_to_locale(pathname);
6356 
6357  if(NULL != pn)
6358  {
6359  res = fu_check_file(pn, NULL);
6360  }
6361 
6362  return(res);
6363 }
6364 
6365 
6366 /* ========================================================================== */
6367 /*! \brief Save string to text file (exported for UI)
6368  *
6369  * \param[in] pathname Pathname of file (UTF-8 encoded)
6370  * \param[in] s String to convert
6371  *
6372  * If the file \e pathname does not exist, it is created.
6373  *
6374  * \note
6375  * The encoding of the pathname is converted to the encoding of the locale.
6376  *
6377  * \return
6378  * - 0 on success
6379  * - Negative value on error
6380  */
6381 
6382 int core_save_to_file(const char* pathname, const char* s)
6383 {
6384  int res = -1;
6385  const char* pn;
6386  int flags = API_POSIX_O_WRONLY | API_POSIX_O_CREAT | API_POSIX_O_TRUNC;
6387  api_posix_mode_t perm = API_POSIX_S_IRUSR | API_POSIX_S_IWUSR |
6388  API_POSIX_S_IRGRP | API_POSIX_S_IWGRP |
6389  API_POSIX_S_IROTH | API_POSIX_S_IWOTH;
6390  int fd = -1;
6391  FILE* fs = NULL;
6392 
6393  pn = core_convert_pathname_to_locale(pathname);
6394  if(NULL != pn)
6395  {
6396  res = fu_open_file(pn, &fd, flags, perm);
6397  if(!res)
6398  {
6399  res = fu_assign_stream(fd, &fs, "w");
6400  if(!res) { fprintf(fs, "%s", s); }
6401  }
6402  fu_close_file(&fd, &fs);
6403  core_free((void*) pn);
6404  }
6405 
6406  return(res);
6407 }
6408 
6409 
6410 /* ========================================================================== */
6411 /*! \brief Create temporary file (exported for UI)
6412  *
6413  * On success the caller is responsible to free the memory allocated for the
6414  * returned pathname.
6415  * Use the function \ref core_tmpfile_delete() to delete the file and free the
6416  * memory block allocated for the pathname.
6417  *
6418  * \note
6419  * This function uses the value of the environment variable \c $TMPDIR for the
6420  * directory. If not set, \c /tmp is used instead.
6421  *
6422  * \return
6423  * - Pointer to pathname of created file
6424  * - NULL on error
6425  */
6426 
6427 const char* core_tmpfile_create(void)
6428 {
6429 #define CORE_PID_MAXLEN (size_t) 32 /* Lower Limit: 15 */
6430  char* tmppathname = NULL;
6431  int rv;
6432  int error = 0;
6433  const char* tmpdir = NULL;
6434  char* pn = NULL;
6435  size_t len_pn;
6436  long int len_pn_max;
6437  size_t len;
6438  long int pid;
6439  char pid_string[CORE_PID_MAXLEN];
6440 
6441  /* Use value of $TMPDIR if set */
6442  rv = ts_getenv("TMPDIR", &tmpdir);
6443  if(0 > rv)
6444  {
6445  /* Fallback to "/tmp" if $TMPDIR is not set */
6446  tmpdir = "/tmp";
6447  }
6448  len = strlen(tmpdir);
6449  pn = api_posix_malloc(++len);
6450  if(NULL != pn) { strcpy(pn, tmpdir); }
6451  if(0 <= rv) { api_posix_free((void*) tmpdir); }
6452 
6453  /* Check for valid directory */
6454  if(NULL != pn)
6455  {
6456  len_pn = strlen(pn);
6457  len_pn_max = api_posix_pathconf(pn, API_POSIX_PC_NAME_MAX);
6458  if(0L > len_pn_max)
6459  {
6460  /* Pathname length check failed, use minimum required by POSIX.1 */
6461  PRINT_ERROR("Temporary file pathname length check failed");
6462  len_pn_max = 14;
6463  }
6464  /* printf("len_pn_max: %u\n", (unsigned int) len_pn_max); */
6465  if(API_POSIX_LONG_MAX > len_pn_max)
6466  {
6467  ++len_pn_max; /* For slash separator */
6468  }
6469 
6470  /* Try to use "CFGNAME_PID_XXXXXX" format for unique name */
6471  len = strlen(CFG_NAME);
6472  len += (size_t) 2; /* For slash and underscore */
6473  pid = (long int) api_posix_getpid();
6474  rv = api_posix_snprintf(pid_string, CORE_PID_MAXLEN, "%ld", pid);
6475  if(0 > rv || CORE_PID_MAXLEN <= (size_t) rv) { error = 1; }
6476  else
6477  {
6478  len += (size_t) rv;
6479  len += (size_t) 7; /* For "_XXXXXX" */
6480  if((size_t) len_pn_max >= len)
6481  {
6482  len_pn += len;
6483  /* One additional byte for NUL termination */
6484  tmppathname = api_posix_realloc((void*) pn, ++len_pn);
6485  if(NULL == tmppathname) { error = 1; }
6486  else
6487  {
6488  strcat(tmppathname, "/" CFG_NAME "_");
6489  strcat(tmppathname, pid_string);
6490  strcat(tmppathname, "_XXXXXX");
6491  }
6492  }
6493  }
6494  /* Check for error */
6495  if(error)
6496  {
6497  /* Name too long => Use truncated $CFG_NAME */
6498  PRINT_ERROR("Temporary file pathname too long"
6499  " (truncated and no longer unique)");
6500  len_pn += 15;
6501  tmppathname = api_posix_realloc((void*) pn, ++len_pn);
6502  if(NULL == tmppathname)
6503  {
6504  api_posix_free((void*) pn);
6505  }
6506  else
6507  {
6508  strncat(tmppathname, "/" CFG_NAME, (size_t) 8);
6509  strcat(tmppathname, "_XXXXXX");
6510  }
6511  }
6512  }
6513 
6514  /* Create temporary file */
6515  if(NULL != tmppathname)
6516  {
6517  rv = api_posix_mkstemp(tmppathname);
6518  if(0 > rv)
6519  {
6520  PRINT_ERROR("Creating temporary file failed");
6521  api_posix_free((void*) tmppathname);
6522  tmppathname = NULL;
6523  }
6524  }
6525 
6526  /* For code review: The caller must 'free()' the memory for 'tmppathname'! */
6527  return(tmppathname);
6528 }
6529 
6530 
6531 /* ========================================================================== */
6532 /*! \brief Delete temporary file (exported for UI)
6533  *
6534  * \param[in] pathname Pathname of file to delete
6535  *
6536  * \attention
6537  * This function automatically free the memory allocated for \e pathname .
6538  * This function should only be used with pathnames that are created with the
6539  * function \ref core_tmpfile_create() .
6540  */
6541 
6542 void core_tmpfile_delete(const char* pathname)
6543 {
6544  if(NULL != pathname)
6545  {
6546  (void) fu_unlink_file(pathname);
6547  api_posix_free((void*) pathname);
6548  }
6549 }
6550 
6551 
6552 /* ========================================================================== */
6553 /*! \brief Close nexus (exported for UI) */
6554 
6556 {
6557  if(NULL != n)
6558  {
6559  nntp_close(&n->nntp_handle, 0);
6560  n->nntp_state = CORE_NEXUS_CLOSED;
6561  }
6562 }
6563 
6564 
6565 /* ========================================================================== */
6566 /*! \brief Lock mutex for data object (exported for UI)
6567  *
6568  * You need to own the mutex before every access to the global object \ref data
6569  * to synchronize the threads.
6570  *
6571  * \attention Unlock the mutex again as soon as possible after the access!
6572  */
6573 
6575 {
6576  int rv;
6577 
6578  rv = api_posix_pthread_mutex_lock(&pt_mutex);
6579  if(rv) { PRINT_ERROR("Locking mutex failed"); }
6580 }
6581 
6582 
6583 /* ========================================================================== */
6584 /*! \brief Unlock mutex for data object (exported for UI)
6585  *
6586  * Call this function after the access to the global object \ref data is
6587  * complete.
6588  */
6589 
6591 {
6592  int rv;
6593 
6594  rv = api_posix_pthread_mutex_unlock(&pt_mutex);
6595  if(rv) { PRINT_ERROR("Unlocking mutex failed"); }
6596 }
6597 
6598 
6599 /* ========================================================================== */
6600 /*! \brief Check whether code is running in UI thread (exported for UI)
6601  *
6602  * \return
6603  * - 0 if not running in UI thread
6604  * - 1 if running in UI thread
6605  * - Negative value on error
6606  */
6607 
6609 {
6610  int res = -1;
6611  int rv;
6612 
6613  if(pt_valid)
6614  {
6615  rv = api_posix_pthread_equal(ui_pt, api_posix_pthread_self());
6616  if(rv) { res = 1; } else { res = 0; }
6617  }
6618 
6619  return(res);
6620 }
6621 
6622 
6623 /* ========================================================================== */
6624 /*! \brief Free an object allocated by core (exported for UI)
6625  *
6626  * Use this function to release dynamic memory that was allocated by the core.
6627  *
6628  * \param[in] p Pointer to object
6629  *
6630  * Release the memory for the object pointed to by \e p.
6631  *
6632  * \note
6633  * The pointer \e p is allowed to be \c NULL and no operation is performed in
6634  * this case.
6635  */
6636 
6637 void core_free(void* p)
6638 {
6639  api_posix_free(p);
6640 }
6641 
6642 
6643 /* ========================================================================== */
6644 /*! \brief Initialize core (exported for UI)
6645  *
6646  * Spawn a new thread for the core.
6647  *
6648  * \return
6649  * - 0 on success
6650  * - Negative value on error
6651  */
6652 
6653 int core_init(void)
6654 {
6655  int res;
6656  api_posix_sigset_t sigmask;
6657  api_posix_sigset_t oldmask;
6658 
6659  /* Store ID of UI thread */
6660  ui_pt = api_posix_pthread_self();
6661 
6662  /* Mask exit signals so that the core thread inherits the new mask */
6663  api_posix_sigemptyset(&sigmask);
6664  api_posix_sigaddset(&sigmask, API_POSIX_SIGINT);
6665  api_posix_sigaddset(&sigmask, API_POSIX_SIGQUIT);
6666  api_posix_sigaddset(&sigmask, API_POSIX_SIGTERM);
6667  res = api_posix_pthread_sigmask(API_POSIX_SIG_BLOCK, &sigmask, &oldmask);
6668  if(res) { PRINT_ERROR("Setting signal mask failed"); }
6669  else
6670  {
6671  /*
6672  * Seed RNG with our PID
6673  *
6674  * Note that this RNG is never used for cryptographic tasks! We can't
6675  * share the cryptographic RNG from the TLS module because it is optional.
6676  * But we need at least some weak random numbers for Message-ID
6677  * generation. They must be available even for minimal configuration
6678  * without TLS module and without XSI extension of operating system.
6679  */
6680  api_posix_srandom((unsigned int) api_posix_getpid());
6681  /* Spawn core thread */
6682  res = api_posix_pthread_create(&pt, NULL, core_main, NULL);
6683  if(res) { PRINT_ERROR("Spawning thread failed"); }
6684  else
6685  {
6686  pt_valid = 1;
6687  /* Restore signal mask for UI thread */
6688  res = api_posix_pthread_sigmask(API_POSIX_SIG_SETMASK, &oldmask, NULL);
6689  if(res)
6690  {
6691  PRINT_ERROR("Restoring signal mask failed");
6692  core_exit();
6693  }
6694  }
6695  }
6696  if(res) res = -1;
6697 
6698  return(res);
6699 }
6700 
6701 
6702 /* ========================================================================== */
6703 /*! \brief Shutdown core (exported for UI)
6704  *
6705  * Cancel the core thread and destroy the article hierarchy.
6706  *
6707  * \note
6708  * It is allowed to call this function after \ref core_init() had failed before.
6709  */
6710 
6711 void core_exit(void)
6712 {
6713  int rv;
6714 
6715  /* Cancel core thread */
6716  if(pt_valid)
6717  {
6718  /* Wait for command queue to become empty (1 second timeout) */
6719  if(main_debug)
6720  {
6721  printf("%s: %sWait for command queue to drain\n",
6722  CFG_NAME, MAIN_ERR_PREFIX);
6723  }
6724  rv = commands_in_queue(10U, 100U);
6725  if(rv) { PRINT_ERROR("Command queue drain timeout"); }
6726  else
6727  {
6728  /* Queue nexus termination command */
6729  core_mutex_lock();
6730  data.cookie = 0;
6731  data.result = -1;
6732  data.data = NULL;
6733  command = CORE_TERMINATE_NEXUS;
6734  rv = api_posix_pthread_cond_signal(&pt_cond);
6735  if(rv) { PRINT_ERROR("Waking up core thread failed"); }
6737 
6738  /* Wait until nexus termination completes (1 second timeout) */
6739  rv = commands_in_queue(10U, 100U);
6740  if(rv) { PRINT_ERROR("Nexus termination failed"); }
6741  }
6742  /* Cancel core thread */
6743  if(main_debug)
6744  {
6745  printf("%s: %sCancel core thread\n", CFG_NAME, MAIN_ERR_PREFIX);
6746  }
6747  rv = api_posix_pthread_cancel(pt);
6748  if(rv) { PRINT_ERROR("Cancelling core thread failed"); }
6749  else
6750  {
6751  /* Join core thread */
6752  if(main_debug)
6753  {
6754  printf("%s: %sJoin core thread\n", CFG_NAME, MAIN_ERR_PREFIX);
6755  }
6756  rv = api_posix_pthread_join(pt, NULL);
6757  if(rv) { PRINT_ERROR("Joining core thread failed"); }
6758  }
6759  }
6760 
6761  /* Destroy article hierarchy */
6763  hierarchy_element_destructor(&h);
6764 }
6765 
6766 
6767 /*! @} */
6768 
6769 /* EOF */
core_mutex_lock
void core_mutex_lock(void)
Lock mutex for data object (exported for UI)
Definition: core.c:6574
nntp_post_article
int nntp_post_article(int, const char *)
Post article.
Definition: nntp.c:3615
core_hierarchy_element::header
struct core_article_header * header
Definition: core.h:143
core_suggest_pathname
const char * core_suggest_pathname(void)
Suggest pathname to save something to a file (exported for UI)
Definition: core.c:5418
secure_clear_memory
void secure_clear_memory(char *, size_t)
Remove string from memory.
Definition: secure.c:73
core_export_group_states
int core_export_group_states(size_t num, struct core_groupstate *list)
Store states of subscribed groups (exported for UI)
Definition: core.c:3563
core_groupdesc
#define core_groupdesc
Group descriptor structure.
Definition: core.h:30
nntp_get_capa_over
int nntp_get_capa_over(int)
Get overview capability of NNTP server.
Definition: nntp.c:3303
core_create_hierarchy_from_overview
void core_create_hierarchy_from_overview(struct core_groupstate *group, struct core_range *range, const char *overview)
Create article hierarchy from header overview (exported for UI)
Definition: core.c:5123
group_reset_states
int group_reset_states(void)
Delete group states.
Definition: group.c:1244
enc_ascii_convert_distribution
void enc_ascii_convert_distribution(char *s)
Convert body of distribution header field.
Definition: encoding.c:4052
CONF_INTRO
Definition: conf.h:51
CONF_FORCE_UNICODE
Definition: conf.h:77
nntp_get_motd
int nntp_get_motd(int, char **, size_t *)
Get message of the day.
Definition: nntp.c:2467
CONF_AUTH
Definition: conf.h:41
core_convert_pathname_to_locale
const char * core_convert_pathname_to_locale(const char *pathname)
Convert pathname to codeset of locale (exported for UI)
Definition: core.c:6261
core_convert_canonical_to_posix
const char * core_convert_canonical_to_posix(const char *s, int rcr, int rlf)
Convert from canonical (RFC 822) to local (POSIX) form.
Definition: core.c:4973
HMAC_SHA2_256_LEN
#define HMAC_SHA2_256_LEN
256 bit
Definition: hmac.h:19
CONF_DIST_SUGG
Definition: conf.h:71
CORE_HE_FLAG_OVER
#define CORE_HE_FLAG_OVER
Definition: core.h:194
core_get_group_list
int core_get_group_list(unsigned int cookie)
Get list of available newsgroups (exported for UI)
Definition: core.c:3325
enc_free
void enc_free(void *p)
Free an object allocated by encoding module.
Definition: encoding.c:7856
core_data::result
int result
Definition: core.h:68
digest_sha1_160
int digest_sha1_160(const char *text, size_t text_len, unsigned char *md)
Secure Hash Algorithm SHA1-160.
Definition: digest.c:159
ui_wakeup
void ui_wakeup(unsigned int cookie)
Wakeup callback (called by core thread after operation has finished)
Definition: gui.cxx:12746
CONF_SERVICE
Definition: conf.h:39
core_data::size
size_t size
Definition: core.h:69
core_hierarchy_action
core_hierarchy_action
Actions that the article hierarchy manager can handle.
Definition: core.h:156
core_range::next
struct core_range * next
Definition: core.h:78
group_sort_list
int group_sort_list(void)
Alphabetically sort list with subscribed groups.
Definition: group.c:952
enc_convert_to_utf8_nfc
const char * enc_convert_to_utf8_nfc(enum enc_mime_cs charset, const char *s)
Convert string from supported character set to Unicode (UTF-8 NFC)
Definition: encoding.c:4777
fu_assign_stream
int fu_assign_stream(int filedesc, FILE **stream, const char *mode)
Assign I/O stream to open file.
Definition: fileutils.c:380
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, const nntp_anum_t *, char **, size_t *)
Get article header.
Definition: nntp.c:3518
filter_get_locale_ctype
enum filter_cs filter_get_locale_ctype(void)
Get codeset of locale category LC_CTYPE.
Definition: filter.c:1816
CORE_HIERARCHY_GETROOT
Definition: core.h:159
nntp_groupdesc::name
char * name
Definition: nntp.h:33
log_get_logpathname
const char * log_get_logpathname(void)
Get logfile pathname.
Definition: log.c:55
enc_mime_cs
enc_mime_cs
IDs for supported MIME character sets.
Definition: encoding.h:59
core_convert_posix_to_canonical
const char * core_convert_posix_to_canonical(const char *s)
Convert from local (POSIX) to canonical (RFC 822) form.
Definition: core.c:4993
core_time_t
unsigned long int core_time_t
Time in seconds since the epoche (in terms of POSIX.1)
Definition: core.h:54
core_get_subscription_proposals
int core_get_subscription_proposals(unsigned int cookie)
Get subscription proposals (exported for UI)
Definition: core.c:4110
CONF_SIGFILE
Definition: conf.h:73
CORE_HIERARCHY_ADD
Definition: core.h:160
core_hierarchy_element::children
size_t children
Definition: core.h:147
core_get_homedir
const char * core_get_homedir(void)
Get home directory of user (exported for UI)
Definition: core.c:5384
enc_mime_word_decode
int enc_mime_word_decode(const char **r, const char *b)
Decode header field containing potential MIME encoded-word tokens.
Definition: encoding.c:5504
core_hierarchy_element::child
struct core_hierarchy_element ** child
Definition: core.h:149
CORE_CL_SECRET_FILE
#define CORE_CL_SECRET_FILE
Definition: core.c:223
fu_read_from_filedesc
int fu_read_from_filedesc(int filedesc, char *buffer, size_t *len)
Read data block to filedescriptor.
Definition: fileutils.c:511
core_range
Article range linked list element.
Definition: core.h:74
group_add
int group_add(struct core_groupstate *group)
Add subscribed group.
Definition: group.c:1101
core_reset_group_states
int core_reset_group_states(unsigned int cookie)
Reset states of subscribed groups (exported for UI)
Definition: core.c:3520
core_groupstate
Group description.
Definition: core.h:82
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)
Get message of the day capability of NNTP server.
Definition: nntp.c:2435
config
struct conf config[CONF_NUM]
Global configuration.
Definition: conf.c:63
enc_convert_ascii_to_anum
int enc_convert_ascii_to_anum(core_anum_t *result, const char *wm, int len)
Convert number from ASCII to numerical format.
Definition: encoding.c:3621
core_get_distribution
int core_get_distribution(const char **dist, const char **groups)
Get distribution suggestions (exported for UI)
Definition: core.c:3893
core_hierarchy_manager
int core_hierarchy_manager(struct core_hierarchy_element **hier, enum core_hierarchy_action action, core_anum_t anum,...)
Manage article hierarchy in memory (exported for UI)
Definition: core.c:5056
core_unsubscribe_group
int core_unsubscribe_group(size_t *num, struct core_groupstate **list, size_t *index)
Remove group from list (exported for UI)
Definition: core.c:3476
core_free
void core_free(void *p)
Free an object allocated by core (exported for UI)
Definition: core.c:6637
CORE_SIG_FLAG_INVALID
#define CORE_SIG_FLAG_INVALID
Definition: core.h:200
CORE_HEADER_LINE_LENGTH
#define CORE_HEADER_LINE_LENGTH
Sufficient for any RFC 5536 conformant header line.
Definition: core.c:207
core_get_cancel_lock
const char * core_get_cancel_lock(unsigned int scheme, const char *mid)
Create Cancel-Lock for Message-ID (exported for UI)
Definition: core.c:5934
conf_entry_val::s
char * s
Definition: conf.h:105
hmac_sha1_160
int hmac_sha1_160(const char *text, size_t text_len, const char *key, size_t key_len, unsigned char *mac)
Message Authentication Code based on SHA1-160 hash algorithm.
Definition: hmac.c:224
group_set_list
int group_set_list(size_t num, struct core_groupstate *grouplist)
Store list with subscribed groups.
Definition: group.c:875
core_mark_as_read
void core_mark_as_read(struct core_groupstate *group, core_anum_t article)
Mark article as read (exported for UI)
Definition: core.c:4773
core_post_article
int core_post_article(const char *article, unsigned int cookie)
Post article (exported for UI)
Definition: core.c:4502
core_article_header::subject
const char * subject
Definition: core.h:112
core_init
int core_init(void)
Initialize core (exported for UI)
Definition: core.c:6653
core_exit
void core_exit(void)
Shutdown core (exported for UI)
Definition: core.c:6711
hmac_sha2_256
int hmac_sha2_256(const char *text, size_t text_len, const char *key, size_t key_len, unsigned char *mac)
Message Authentication Code based on SHA2-256 hash algorithm.
Definition: hmac.c:250
core_check_thread_ui
int core_check_thread_ui(void)
Check whether code is running in UI thread (exported for UI)
Definition: core.c:6608
core_hierarchy_element::anum
core_anum_t anum
Definition: core.h:139
nntp_grouplabel
NNTP group label (description string)
Definition: nntp.h:41
core_range::first
core_anum_t first
Definition: core.h:76
CORE_CL_SHA256
#define CORE_CL_SHA256
Definition: core.h:209
secure_cl_secret
void secure_cl_secret(const char *)
Generate file with new secret if CL secret file is missing.
Definition: secure.c:139
core_get_subscribed_group_info
int core_get_subscribed_group_info(const size_t *num, struct core_groupstate **list, unsigned int cookie)
Get information about subscribed groups (exported for UI)
Definition: core.c:3693
nntp_groupdesc
NNTP group descriptor.
Definition: nntp.h:31
enc_wm_pattern
Wildmat array element (for RFC 3977 wildmat-pattern)
Definition: encoding.h:139
core_tmpfile_delete
void core_tmpfile_delete(const char *pathname)
Delete temporary file (exported for UI)
Definition: core.c:6542
core_sort_group_list
int core_sort_group_list(void)
Alphabetically sort group list (exported for UI)
Definition: core.c:3424
main_debug
int main_debug
Enable additional debug output if nonzero.
Definition: main.cxx:64
core_get_motd
int core_get_motd(unsigned int cookie)
Get message of the day (exported for UI)
Definition: core.c:3830
core_subscribe_group
int core_subscribe_group(const char *name)
Store group subscription (exported for UI)
Definition: core.c:3444
group_article_range_destructor
void group_article_range_destructor(struct core_range **list)
Destructor for linked list of article ranges.
Definition: group.c:771
core_mutex_unlock
void core_mutex_unlock(void)
Unlock mutex for data object (exported for UI)
Definition: core.c:6590
group_article_range_constructor
int group_article_range_constructor(struct core_range **range, core_anum_t start, core_anum_t end, struct core_range *next)
Article range constructor.
Definition: group.c:746
enc_timestamp_decode
core_time_t enc_timestamp_decode(const char *timestamp)
Decode canonical timestamp to POSIX time (seconds since epoche)
Definition: encoding.c:3171
core_get_group_labels
int core_get_group_labels(unsigned int cookie)
Get list of newsgroup labels (exported for UI)
Definition: core.c:3384
nntp_group_descriptor_constructor
struct nntp_groupdesc * nntp_group_descriptor_constructor(const char *)
Allocate and initialize a descriptor for group.
Definition: nntp.c:2657
fu_check_file
int fu_check_file(const char *pathname, api_posix_struct_stat *state)
Check whether file exist.
Definition: fileutils.c:214
enc_ascii_check_printable
int enc_ascii_check_printable(const char *s)
Check for printable ASCII characters.
Definition: encoding.c:4000
core_hierarchy_element::parent
struct core_hierarchy_element * parent
Definition: core.h:145
CORE_HIERARCHY_INIT
Definition: core.h:158
core_get_msgid
const char * core_get_msgid(const char *fqdn)
Get globally unique Message-ID (exported for UI)
Definition: core.c:5638
nntp_groupdesc::lwm
nntp_anum_t lwm
Definition: nntp.h:35
core_destroy_subscribed_group_info
void core_destroy_subscribed_group_info(struct core_groupdesc **list)
Destructor for subscribed group information (exported for UI)
Definition: core.c:3740
conf_entry_val::i
int i
Definition: conf.h:104
core_destroy_subscribed_group_states
void core_destroy_subscribed_group_states(size_t *num, struct core_groupstate **list)
Destructor for subscribed group states (exported for UI)
Definition: core.c:3649
fu_create_path
int fu_create_path(const char *path, api_posix_mode_t perm)
Create path.
Definition: fileutils.c:122
group_get_list
int group_get_list(size_t *num, struct core_groupstate **grouplist)
Get list with subscribed groups.
Definition: group.c:829
core_article_header::refs
const char ** refs
Definition: core.h:119
nntp_open
int nntp_open(int *, const char *, const char *, const char *, int, int,...)
Open connection to NNTP server.
Definition: nntp.c:2084
CONF_INEWS
Definition: conf.h:58
db_update_groups
int db_update_groups(size_t groupcount, const char **grouplist)
Delete database content for all groups that are not specified.
Definition: database.c:380
nntp_get_group_labels
int nntp_get_group_labels(int, size_t *, struct nntp_grouplabel **)
Get additional group information.
Definition: nntp.c:2958
nntp_get_article
int nntp_get_article(int, const nntp_anum_t *, char **, size_t *)
Get complete article.
Definition: nntp.c:3468
core_check_file_exist
int core_check_file_exist(const char *pathname)
Check wheter file exists (exported for UI)
Definition: core.c:6352
CORE_NEXUS_RETRIES
#define CORE_NEXUS_RETRIES
Number of retries for nexus operations.
Definition: core.c:201
enc_mime_para_decode
int enc_mime_para_decode(const char **r, const char *b, int m)
Decode header field containing potential MIME parameters.
Definition: encoding.c:5803
CORE_CL_SHA1
#define CORE_CL_SHA1
Definition: core.h:208
core_article_header
Article header fields supported by core.
Definition: core.h:107
fu_unlink_file
int fu_unlink_file(const char *pathname)
Unlink file.
Definition: fileutils.c:362
core_groupstate::name
const char * name
Definition: core.h:84
enc_uc_check_utf8
int enc_uc_check_utf8(const char *s)
Verify UTF-8 encoding.
Definition: encoding.c:4140
CORE_HIERARCHY_ADD_OVER
Definition: core.h:161
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
core_hierarchy_element
Node in article hierarchy.
Definition: core.h:136
core_mark_as_unread
void core_mark_as_unread(struct core_groupstate *group, core_anum_t article)
Mark article as unread (exported for UI)
Definition: core.c:4878
PRINT_ERROR
#define PRINT_ERROR(s)
Prepend module prefix and print error message.
Definition: main.h:19
core_article_header::supers
const char * supers
Definition: core.h:114
CONF_TS_LTIME
Definition: conf.h:74
data
struct core_data data
Global data object (shared by all threads)
Definition: core.c:242
core_data::cookie
unsigned int cookie
Definition: core.h:67
core_get_datetime
const char * core_get_datetime(int force_utc)
Get current date and time in RFC 5322 format (exported for UI)
Definition: core.c:5493
ts_getenv
int ts_getenv(const char *, const char **)
Thread safe replacement for getenv()
Definition: ts_functions.c:136
enc_destroy_wildmat
void enc_destroy_wildmat(struct enc_wm_pattern **obj, int num)
Destroy wildmat pattern array.
Definition: encoding.c:4516
nntp_set_group
int nntp_set_group(int, const char *, struct nntp_groupdesc **)
Set current group.
Definition: nntp.c:3146
CONF_PASS
Definition: conf.h:44
DIGEST_SHA2_256_LEN
#define DIGEST_SHA2_256_LEN
256 bit
Definition: digest.h:19
ext_inews
int ext_inews(const char *article)
Start external inews for article injection.
Definition: extutils.c:911
DIGEST_SHA1_160_LEN
#define DIGEST_SHA1_160_LEN
160 bit
Definition: digest.h:18
core_destroy_entity_header
void core_destroy_entity_header(struct core_article_header **ehp)
Destructor for MIME multipart entity header object (exported for UI)
Definition: core.c:5366
nntp_get_overview
int nntp_get_overview(int, nntp_anum_t, nntp_anum_t, char **, size_t *)
Get overview for article range.
Definition: nntp.c:3368
CONF_USER
Definition: conf.h:43
xdg_get_confdir
const char * xdg_get_confdir(const char *)
Get configuration directory.
Definition: xdg.c:116
nntp_get_grouplist
int nntp_get_grouplist(int, size_t *, struct nntp_groupdesc **)
Get group list.
Definition: nntp.c:2702
CORE_SIG_FLAG_SEPARATOR
#define CORE_SIG_FLAG_SEPARATOR
Definition: core.h:199
db_read
int db_read(const char *group, core_anum_t anum, char **header, size_t *len)
Read entry.
Definition: database.c:583
core_get_article_body
int core_get_article_body(const core_anum_t *id, unsigned int cookie)
Get article body (exported for UI)
Definition: core.c:4428
db_add
int db_add(const char *group, core_anum_t anum, const char *header, size_t len)
Add entry.
Definition: database.c:464
xdg_append_to_path
int xdg_append_to_path(const char **, const char *)
Append path component to buffer.
Definition: xdg.c:55
nntp_get_article_body
int nntp_get_article_body(int, const nntp_anum_t *, char **, size_t *)
Get article body.
Definition: nntp.c:3568
core_hierarchy_element::flags
unsigned int flags
Definition: core.h:141
core_get_cancel_key
const char * core_get_cancel_key(unsigned int scheme, const char *msgid)
Create Cancel-Key for Message-ID (exported for UI)
Definition: core.c:5772
enc_ascii_convert_to_printable
void enc_ascii_convert_to_printable(char *s)
Convert to printable ASCII format.
Definition: encoding.c:4027
enc_convert_posix_to_canonical
const char * enc_convert_posix_to_canonical(const char *s)
Convert from local (POSIX) to canonical (RFC 822) form.
Definition: encoding.c:4679
nntp_close
void nntp_close(int *, unsigned int)
Disconnect from NNTP server.
Definition: nntp.c:2364
CONF_TS_COMMENT
Definition: conf.h:75
CORE_HIERARCHY_UPDATE
Definition: core.h:162
time_msleep
int time_msleep(unsigned int)
Milisecond delay.
Definition: timing.c:48
enc_create_wildmat
int enc_create_wildmat(struct enc_wm_pattern **obj, const char *wm)
Create wildmat pattern array.
Definition: encoding.c:4349
core_get_article_by_mid
int core_get_article_by_mid(const char *mid, unsigned int cookie)
Get complete article by Message-ID (exported for UI)
Definition: core.c:4242
db_delete
int db_delete(const char *group, core_anum_t start, core_anum_t end)
Delete entries.
Definition: database.c:665
nntp_get_distrib_pats
int nntp_get_distrib_pats(int, const char **, size_t *)
Get distribution patterns.
Definition: nntp.c:2530
core_article_header::date
core_time_t date
Definition: core.h:113
core_range::last
core_anum_t last
Definition: core.h:77
core_check_already_read
int core_check_already_read(struct core_groupstate *group, struct core_hierarchy_element *article)
Check whether article was alread read (exported for UI)
Definition: core.c:4748
core_get_signature
const char * core_get_signature(unsigned int *warnings)
Get signature for outgoing messages (exported for UI)
Definition: core.c:6044
fu_close_file
void fu_close_file(int *filedesc, FILE **stream)
Close file (and potentially associated I/O stream)
Definition: fileutils.c:297
CONF_INV_ORDER
Definition: conf.h:69
conf::val
union conf_entry_val val
Definition: conf.h:113
NNTP_CLOSE_NOQUIT
#define NNTP_CLOSE_NOQUIT
Definition: nntp.h:57
core_data::data
void * data
Definition: core.h:70
core_tmpfile_create
const char * core_tmpfile_create(void)
Create temporary file (exported for UI)
Definition: core.c:6427
nntp_get_over_newsgroups_index
int nntp_get_over_newsgroups_index(int, size_t *)
Get index of Newsgroups header field in overview.
Definition: nntp.c:3334
core_get_overview
int core_get_overview(struct core_range *range, unsigned int cookie)
Get article header overview (exported for UI)
Definition: core.c:4173
enc_mime_encode_base64
int enc_mime_encode_base64(const char **enc, const char *data, size_t len)
Encode binary data to base64.
Definition: encoding.c:3761
enc_convert_canonical_to_posix
const char * enc_convert_canonical_to_posix(const char *s, int rcr, int rlf)
Convert from canonical (RFC 822) to local (POSIX) form.
Definition: encoding.c:4558
enc_lines_decode
unsigned long int enc_lines_decode(const char *lines)
Decode number of lines.
Definition: encoding.c:3115
CONF_ENC
Definition: conf.h:40
core_get_article_header
int core_get_article_header(const core_anum_t *id, unsigned int cookie)
Get article header (exported for UI)
Definition: core.c:4366
core_update_subscribed_group_states
int core_update_subscribed_group_states(size_t *num, struct core_groupstate **list, size_t *index)
Get states of subscribed groups (exported for UI)
Definition: core.c:3589
ENC_CS_UTF_8
Definition: encoding.h:99
core_extract_groups
const char ** core_extract_groups(const char *body)
Extract groups from 'Newsgroups' header field (exported for UI)
Definition: core.c:3292
nntp_anum_t
unsigned long int nntp_anum_t
Article number.
Definition: nntp.h:28
CONF_CANCELKEY
Definition: conf.h:55
core_disconnect
void core_disconnect(void)
Close nexus (exported for UI)
Definition: core.c:6555
CORE_SIG_FLAG_NOTASCII
#define CORE_SIG_FLAG_NOTASCII
Definition: core.h:201
CONF_SERVER
Definition: conf.h:38
core_groupstate::info
struct core_range * info
Definition: core.h:85
db_clear
int db_clear(void)
Delete all database content.
Definition: database.c:346
fu_check_path
int fu_check_path(const char *path)
Check path.
Definition: fileutils.c:76
group_destroy_list
void group_destroy_list(size_t *num, struct core_groupstate **grouplist)
Destroy list of subscribed groups.
Definition: group.c:798
core_get_article
int core_get_article(const core_anum_t *id, unsigned int cookie)
Get complete article (exported for UI)
Definition: core.c:4304
ENC_CS_ISO8859_1
Definition: encoding.h:63
CONF_IMMEDAUTH
Definition: conf.h:42
core_entity_parser
const char * core_entity_parser(const char *entity, size_t len, struct core_article_header **e_h, size_t *e_len)
Parse header of MIME multipart entity (exported for UI)
Definition: core.c:5302
core_save_to_file
int core_save_to_file(const char *pathname, const char *s)
Save string to text file (exported for UI)
Definition: core.c:6382
core_data
Structure to transfer data between core and UI threads.
Definition: core.h:65
core_groupstate::last_viewed
core_anum_t last_viewed
Definition: core.h:86
HMAC_SHA1_160_LEN
#define HMAC_SHA1_160_LEN
160 bit
Definition: hmac.h:18
digest_sha2_256
int digest_sha2_256(const char *text, size_t text_len, unsigned char *md)
Secure Hash Algorithm SHA2-256.
Definition: digest.c:182
enc_convert_to_8bit
const char * enc_convert_to_8bit(enum enc_mime_cs *charset, const char *s, const char **cs_iana)
Convert string from Unicode (UTF-8 NFC) to an 8bit character set.
Definition: encoding.c:4991
core_article_header::msgid
const char * msgid
Definition: core.h:109
fu_read_whole_file
int fu_read_whole_file(int filedesc, char **buffer, size_t *len)
Read text file content and store it into memory buffer.
Definition: fileutils.c:452
MAIN_ERR_PREFIX
#define MAIN_ERR_PREFIX
Message prefix for CORE module.
Definition: core.c:194
CORE_SIG_FLAG_LENGTH
#define CORE_SIG_FLAG_LENGTH
Definition: core.h:202
fu_open_file
int fu_open_file(const char *pathname, int *filedesc, int mode, api_posix_mode_t perm)
Open file.
Definition: fileutils.c:246
nntp_get_article_by_mid
int nntp_get_article_by_mid(int, const char *, char **, size_t *)
Get complete article via Message-ID.
Definition: nntp.c:3418
nntp_get_subscriptions
int nntp_get_subscriptions(int, char **, size_t *)
Get subscription proposals.
Definition: nntp.c:2604
core_get_introduction
const char * core_get_introduction(const char *ca, const char *ngl)
Get introduction line for citation (exported for UI)
Definition: core.c:6164
core_set_group
int core_set_group(const char *name, unsigned int cookie)
Set current group (exported for UI)
Definition: core.c:3771

Generated at 2026-01-27 using  doxygen