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

Generated at 2024-04-27 using  doxygen