extutils.c
Go to the documentation of this file.
1 /* ========================================================================== */
2 /*! \file
3  * \brief External program delegation functions
4  *
5  * Copyright (c) 2012-2024 by the developers. See the LICENSE file for details.
6  *
7  * If nothing else is specified, functions return zero to indicate success
8  * and a negative value to indicate an error.
9  */
10 
11 
12 /* ========================================================================== */
13 /* Include headers */
14 
15 #include "posix.h" /* Include this first because of feature test macros */
16 
17 #include <ctype.h>
18 #include <stdarg.h>
19 #include <string.h>
20 
21 #include "conf.h"
22 #include "encoding.h"
23 #include "extutils.h"
24 #include "fileutils.h"
25 #include "http.h"
26 #include "main.h"
27 #include "sighandler.h"
28 
29 
30 /* ========================================================================== */
31 /*! \defgroup EXTUTILS EXT: Delegation to external utilities
32  *
33  * This module calls external programs to delegate functionality like sending
34  * E-Mail or displaying HTML.
35  *
36  * \attention
37  * It is required that 'PID_MAX' is not larger than 'LONG_MAX' (must be checked
38  * by build system).
39  */
40 /*! @{ */
41 
42 
43 /* ========================================================================== */
44 /* Constants */
45 
46 /*! \brief Message prefix for EXTUTILS module */
47 #define MAIN_ERR_PREFIX "EXT: "
48 
49 
50 /* ========================================================================== */
51 /* Verify and make data usable as parameter for xdg-utils
52  *
53  * \param[in] s UTF-8 string to check
54  * \param[in] conv Allows modification if true
55  *
56  * This function verifies that the string \e s can be passed as parameter for
57  * xdg-utils on the command line of a POSIX shell between single quotes.
58  *
59  * If the data contains apostrophe characters (U+0027) an error is returned if
60  * \e conv is false. Otherwise all such characters will be replaced with the
61  * Unicode codepoint RIGHT SINGLE QUOTATION MARK (U+2019).
62  *
63  * \note
64  * It is allowed to pass \c NULL for parameter \e s (this is handled as a
65  * regular error condition).
66  *
67  * \return
68  * - Pointer to new memory block with UTF-8 string on success
69  * - NULL on error
70  */
71 
72 static char* ext_check_data(const char* s, int conv)
73 {
74  char* res = NULL;
75  const char* rsqm = "\xE2\x80\x99"; /* U+2019 in UTF-8 format */
76  size_t len;
77  size_t i = 0;
78  size_t ri;
79  int error = 0;
80 
81  if(NULL != s)
82  {
83  len = strlen(s);
84  /* Check for ' character */
85  if(NULL == strchr(s, 0x27))
86  {
87  /* No => Copy data unchanged */
88  res = (char*) api_posix_malloc(++len); /* One byte for NUL */
89  if(NULL != res) { memcpy(res, s, len); }
90  }
91  else if(conv)
92  {
93  /* Yes => Convert the data as requested */
94  printf("%s: %sApostrophe replacement U+0027 => U+2019 done\n",
95  CFG_NAME, MAIN_ERR_PREFIX);
96  while(s[i])
97  {
98  if(0x27 == (int) s[i++])
99  {
100  if(API_POSIX_SIZE_MAX - len < (size_t) 2)
101  {
102  PRINT_ERROR("Aborted before \x27size_t\x27 overflow");
103  error = 1;
104  }
105  else { len += 2; }
106  }
107  }
108  if(!error)
109  {
110  res = (char*) api_posix_malloc(++len); /* One byte for NUL */
111  if(NULL != res)
112  {
113  i = 0; ri = 0;
114  do
115  {
116  if(0x27 != (int) s[i]) { res[ri++] = s[i]; }
117  else
118  {
119  res[ri++] = rsqm[0];
120  res[ri++] = rsqm[1];
121  res[ri++] = rsqm[2];
122  }
123  }
124  while(s[++i]);
125  /* Terminate result string */
126  res[ri] = 0;
127  }
128  }
129  }
130  }
131 
132  return(res);
133 }
134 
135 
136 /* ========================================================================== */
137 /*! \brief Send e-mail to single recipient
138  *
139  * \param[in] recipient Recipient (URI or RFC 5322 conformant \c addr-spec )
140  * \param[in] subject Subject (UTF-8 NFC encoded) or \c NULL
141  * \param[in] body Cited content for body (UTF-8 NFC encoded) or \c NULL
142  *
143  * This function calls the external program \c xdg-email to handle the e-mail
144  * processing.
145  *
146  * If \e recipient is an URI with \c mailto scheme, the parameters \e subject
147  * and \e body should be \c NULL and are ignored otherwise.
148  *
149  * \attention
150  * If \e subject or \e body contain line breaks, they must be in POSIX form
151  * (single LF).
152  * According to RFC 2368 (Section 5) this is different for an URI, therefore if
153  * an URI is passed as \e recipient , line breaks inside parameters must be in
154  * canonical form (CR+LF, %0D%0A with percent encoding).
155  *
156  * \attention
157  * The Unicode data is expected to be valid (encoding and normalization must
158  * be verified by the caller).
159  *
160  * \note
161  * If \e recipient is \c NULL the functions return an error. Therefore this
162  * must not be checked by the caller.
163  *
164  * \return
165  * - 0 if e-mail program was successfully started
166  * - Negative value if start of shell failed
167  * - Positive value if shell reported an error
168  */
169 
170 int ext_handler_email(const char* recipient, const char* subject,
171  const char* body)
172 {
173  int res = -1;
174  const char* mailto = "mailto:";
175  const char* command = "xdg-email --utf8";
176  const char* opt_subject = " --subject \x27";
177  const char* opt_body = " --body \x27";
178  int error = 1;
179  int uri = 0;
180  char* buf = NULL;
181  char* tmp;
182  size_t len = 0;
183  size_t len_tmp;
184  size_t x;
185  FILE* fs;
186 
187  /* Check for URI with scheme 'mailto' */
188  if(NULL != recipient)
189  {
190  len = strlen(mailto);
191  if(strlen(recipient) >= len && !strncmp(recipient, mailto, len))
192  {
193  /* Found => This means the data should already have RFC 2368 format */
194  uri = 1;
195  }
196  }
197 
198  /*
199  * Verify data
200  * The variables 'recipient', 'subject' and 'body' must be prepared so that
201  * 'free()' can be called on them at the end.
202  */
203  if(NULL == recipient)
204  {
205  PRINT_ERROR("No e-mail recipient specified");
206  subject = NULL;
207  body = NULL;
208  }
209  else
210  {
211  /* Non US-ASCII characters are not allowed without percent encoding */
212  if(uri && enc_ascii_check(recipient))
213  {
214  PRINT_ERROR("Invalid characters in URI");
215  recipient = NULL;
216  subject = NULL;
217  body = NULL;
218  }
219  else
220  {
221  recipient = ext_check_data(recipient, 0);
222  if(NULL == recipient)
223  {
224  if(uri)
225  {
226  PRINT_ERROR("URI with \x27mailto\x27 scheme cannot be handled");
227  }
228  else
229  {
230  PRINT_ERROR("e-mail recipient address cannot be handled");
231  }
232  subject = NULL;
233  body = NULL;
234  }
235  else
236  {
237  /* Recipient looks good => Start processing */
238  printf("%s: %se-mail delegation to xdg-email for: %s\n",
239  CFG_NAME, MAIN_ERR_PREFIX, recipient);
240  error = 0;
241 
242  /* Ignore subject and body if not usable */
243  if(NULL != subject)
244  {
245  subject = ext_check_data(subject, 1);
246  if(NULL == subject)
247  {
248  PRINT_ERROR("e-mail subject cannot be handled (ignored)");
249  }
250  }
251  if(NULL != body)
252  {
253  body = ext_check_data(body, 1);
254  if(NULL == body)
255  {
256  PRINT_ERROR("e-mail body cannot be handled (ignored)");
257  }
258  }
259  }
260  }
261  }
262 
263  /* Create command line */
264  if(!error)
265  {
266  len = strlen(command);
267  buf = (char*) api_posix_malloc(++len); /* One byte for NUL */
268  if(NULL == buf) { error = 1; }
269  else { strncpy(buf, command, len); }
270  }
271 
272  /* Append subject if present */
273  if(!error && !uri && NULL != subject)
274  {
275  len_tmp = strlen(subject);
276  x = strlen(opt_subject) + (size_t) 1; /* One additional byte for ' */
277  if(API_POSIX_SIZE_MAX - len_tmp < x) { error = 1; }
278  else
279  {
280  if(API_POSIX_SIZE_MAX - len < len_tmp + x) { error = 1; }
281  else
282  {
283  len += len_tmp + x;
284  tmp = (char*) api_posix_realloc(buf, len);
285  if(NULL == tmp) { error = 1; }
286  else
287  {
288  buf = tmp;
289  strcat(buf, opt_subject);
290  strncat(buf, subject, len_tmp);
291  strcat(buf, "\x27");
292  }
293  }
294  }
295  }
296 
297  /* Append body if present */
298  if(!error && !uri && NULL != body)
299  {
300  len_tmp = strlen(body);
301  x = strlen(opt_body) + (size_t) 1; /* One additional byte for ' */
302  if(API_POSIX_SIZE_MAX - len_tmp < x) { error = 1; }
303  else
304  {
305  if(API_POSIX_SIZE_MAX - len < len_tmp + x) { error = 1; }
306  else
307  {
308  len += len_tmp + x;
309  tmp = (char*) api_posix_realloc(buf, len);
310  if(NULL == tmp) { error = 1; }
311  else
312  {
313  buf = tmp;
314  strcat(buf, opt_body);
315  strncat(buf, body, len_tmp);
316  strcat(buf, "\x27");
317  }
318  }
319  }
320  }
321 
322  /* Append recipient */
323  if(!error)
324  {
325  len_tmp = strlen(recipient);
326  if(API_POSIX_SIZE_MAX - len_tmp < (size_t) 3) { error = 1; }
327  else
328  {
329  if(API_POSIX_SIZE_MAX - len < len_tmp + (size_t) 3) { error = 1; }
330  else
331  {
332  /* 3 additional bytes for space, leading and trailing ' */
333  len += len_tmp + (size_t) 3;
334  tmp = (char*) api_posix_realloc(buf, len);
335  if(NULL == tmp) { error = 1; }
336  else
337  {
338  buf = tmp;
339  strcat(buf, " \x27");
340  strncat(buf, recipient, len_tmp);
341  strcat(buf, "\x27");
342  }
343  }
344  }
345  }
346 
347 
348  /* Spawn new process for e-mail handling */
349  if(!error)
350  {
351  fs = api_posix_popen(buf, "w");
352  if(NULL != fs) { res = api_posix_pclose(fs); }
353  }
354 
355  /* Release memory */
356  api_posix_free((void*) body);
357  api_posix_free((void*) subject);
358  api_posix_free((void*) recipient);
359  api_posix_free((void*) buf);
360 
361  return(res);
362 }
363 
364 
365 /* ========================================================================== */
366 /*! \brief Start external handler for URI
367  *
368  * \param[in] uri Pointer to URI string
369  * \param[out] invalid Pointer to invalid encoding flag
370  *
371  * If the URI encoding of \e uri is invalid, a negative value is returned and
372  * a nonzero value is written to the location pointed to by \e invalid .
373  * Otherwise zero is written to the location pointed to by \e invalid .
374  *
375  * This function calls the external program \c xdg-open to handle the URI.
376  *
377  * \attention
378  * Because the current version of \c xdg-open starts a WWW browser, call this
379  * function only for URIs that typically can be handled by such programs
380  * (like \c http:// or \c ftp:// types).
381  *
382  * \note
383  * On Apple platform, the program \c open is used to handle the URI.
384  *
385  * \return
386  * - 0 if external URI handler was successfully started
387  * - Negative value if start of shell failed
388  * - Positive value if shell reported an error
389  */
390 
391 int ext_handler_uri(const char* uri, int* invalid)
392 {
393  int res = -1;
394 #ifdef __APPLE__
395  const char* command = "open";
396 #else /* __APPLE__ */
397  const char* command = "xdg-open";
398 #endif /* __APPLE__ */
399  int error = 1;
400  char* buf = NULL;
401  char* tmp;
402  size_t len = 0;
403  size_t len_tmp;
404  FILE* fs;
405 
406  /*
407  * Verify data
408  * The variable 'uri' must be prepared so that 'free()' can be called on it
409  * at the end.
410  */
411  *invalid = 1;
412  if(NULL == uri) { PRINT_ERROR("No URI specified"); }
413  else
414  {
415  /* Non US-ASCII characters are not allowed without percent encoding */
416  if(enc_ascii_check(uri))
417  {
418  PRINT_ERROR("Invalid characters in URI");
419  uri = NULL;
420  }
421  else
422  {
423  uri = ext_check_data(uri, 0);
424  if(NULL == uri)
425  {
426  PRINT_ERROR("URI cannot be handled");
427  }
428  else
429  {
430  /* URI passed verification */
431  *invalid = 0;
432  error = 0;
433  }
434  }
435  }
436 
437  /* Create command line */
438  if(!error)
439  {
440  printf("%s: %sURI delegation to %s for: %s\n",
441  CFG_NAME, MAIN_ERR_PREFIX, command, uri);
442  len = strlen(command);
443  buf = (char*) api_posix_malloc(++len); /* One byte for NUL */
444  if(NULL == buf) { error = 1; }
445  else { strncpy(buf, command, len); }
446  }
447 
448  /* Append recipient */
449  if(!error)
450  {
451  len_tmp = strlen(uri);
452  if(API_POSIX_SIZE_MAX - len_tmp < (size_t) 5) { error = 1; }
453  else
454  {
455  if(API_POSIX_SIZE_MAX - len < len_tmp + (size_t) 5) { error = 1; }
456  else
457  {
458  /* 5 additional bytes for spaces, leading/trailing ' and & */
459  len += len_tmp + (size_t) 5;
460  tmp = (char*) api_posix_realloc(buf, len);
461  if(NULL == tmp) { error = 1; }
462  else
463  {
464  buf = tmp;
465  strcat(buf, " \x27");
466  strcat(buf, uri);
467  strcat(buf, "\x27 &");
468  }
469  }
470  }
471  }
472 
473  /* Spawn new process for URI handling */
474  if(!error)
475  {
476  fs = api_posix_popen(buf, "w");
477  if(NULL != fs) { res = api_posix_pclose(fs); }
478  }
479 
480  /* Release memory */
481  api_posix_free((void*) uri);
482  api_posix_free((void*) buf);
483 
484  return(res);
485 }
486 
487 
488 /* ========================================================================== */
489 /*! \brief Start external editor for article composition
490  *
491  * \param[in] tfpn Pathname of temporary file (with UTF-8 content) to edit
492  * \param[in] async Flag indicating that function should return immediately
493  *
494  * The name of the external editor is taken from the configfile. It may not be
495  * a full pathname, the editor is searched via \c $PATH in this case.
496  *
497  * If \e async is zero, the calling thread is blocked until the process with
498  * the editor has terminated. No additional parameter should be passed.
499  *
500  * If \e async is nonzero, a third parameter of type (long int*) must be passed
501  * by the caller. This function will write the PID of the editor process to this
502  * location and the editor will be started asynchronously.
503  * If this function returns success, the status of the external editor can be
504  * polled with the function \ref ext_editor_status() using the returned PID.
505  *
506  * \attention
507  * The caller must ensure that the file associated with \e tfpn is not modifed
508  * by the caller until editing is finished (either synchronous or asynchronous).
509  *
510  * \return
511  * - 0 if the file was successfully edited or asynchronous processing has been
512  * started successfully
513  * - Negative value on local error
514  * - Positive value if external editor reported error
515  */
516 
517 int ext_editor(const char* tfpn, int async, ...)
518 {
519  va_list ap; /* Object for argument list handling */
520  int res = -1;
521  const char* epn = config[CONF_EDITOR].val.s;
522  size_t len = strlen(epn);
523  api_posix_pid_t pid;
524  int rv;
525  api_posix_pid_t rv2;
526  int status = -1;
527 
528  if(len && NULL != tfpn)
529  {
530  if(main_debug)
531  {
532  printf("%s: %sExternal editor (path)name: \x22%s\x22\n",
533  CFG_NAME, MAIN_ERR_PREFIX, epn);
534  }
535  /* Spawn child process for editor */
536  pid = api_posix_fork();
537  if(!pid)
538  {
539  /* Executed by child process */
541  if(!rv) { api_posix_execlp(epn, epn, tfpn, (char*) NULL); }
542  PRINT_ERROR("Executing external editor failed");
543  exit(API_POSIX_EXIT_FAILURE);
544  }
545  else if(-1 != pid)
546  {
547  res = 0;
548  if(async)
549  {
550  /* Return PID to caller */
551  va_start(ap, async);
552  *(va_arg(ap, long int*)) = (long int) pid;
553  va_end(ap);
554  }
555  else
556  {
557  /* Get exit status of editor */
558  do { rv2 = api_posix_waitpid(pid, &status, 0); }
559  while((api_posix_pid_t) -1 == rv2
560  && API_POSIX_EINTR == api_posix_errno);
561  if(rv2 != pid)
562  {
563  PRINT_ERROR("Child process not found (bug)");
564  res = -1;
565  }
566  else
567  {
568  if(!status) { res = 0; }
569  else
570  {
571  PRINT_ERROR("External editor reported error");
572  if(!API_POSIX_WIFEXITED(status))
573  {
574  res = API_POSIX_EXIT_FAILURE;
575  }
576  else { res = API_POSIX_WEXITSTATUS(status); }
577  }
578  }
579  }
580  }
581  }
582 
583  return(res);
584 }
585 
586 
587 /* ========================================================================== */
588 /*! \brief Poll status of external editor
589  *
590  * \param[in] editor_pid PID of external editor
591  *
592  * \attention
593  * The value \e editor_pid must correspond to a value returned by
594  * \ref ext_editor() in asynchronous mode.
595  *
596  * \attention
597  * It is not allowed to call this function again for the same value of
598  * \e editor_pid after either success or error was returned.
599  *
600  * \return
601  * - 0 Success
602  * - -1 if external editor is still running
603  * - Other negative value on local error
604  * - Positive value if external editor reported error
605  */
606 
607 int ext_editor_status(long int editor_pid)
608 {
609  int res = -1;
610  api_posix_pid_t pid = (api_posix_pid_t) editor_pid;
611  api_posix_pid_t rv2;
612  int status = -1;
613 
614  /* Get exit status of editor */
615  do { rv2 = api_posix_waitpid(pid, &status, API_POSIX_WNOHANG); }
616  while((api_posix_pid_t) -1 == rv2 && API_POSIX_EINTR == api_posix_errno);
617  if(rv2)
618  {
619  if(rv2 != pid)
620  {
621  PRINT_ERROR("Child process not found (bug)");
622  res = -2;
623  }
624  else
625  {
626  if(!status) { res = 0; }
627  else
628  {
629  PRINT_ERROR("External editor reported error");
630  if(!API_POSIX_WIFEXITED(status)) { res = API_POSIX_EXIT_FAILURE; }
631  else { res = API_POSIX_WEXITSTATUS(status); }
632  }
633  }
634  }
635 
636  return(res);
637 }
638 
639 
640 /* ========================================================================== */
641 /*! \brief Terminate external editor
642  *
643  * \param[in] editor_pid PID of external editor
644  *
645  * \attention
646  * The value \e editor_pid must correspond to a value returned by
647  * \ref ext_editor() in asynchronous mode.
648  *
649  * \attention
650  * It is not allowed to call this function multiple times for the same editor.
651  */
652 
653 void ext_editor_terminate(long int editor_pid)
654 {
655  api_posix_kill((api_posix_pid_t) editor_pid, API_POSIX_SIGTERM);
656 }
657 
658 
659 /* ========================================================================== */
660 /*! \brief External post processing filter for outgoing articles
661  *
662  * \param[in] article Pointer to article (in canonical form)
663  *
664  * \note
665  * The body of \e article is still in Unicode and the MIME content type is not
666  * added to the header yet.
667  *
668  * The pathname of the external filter is taken from the configfile.
669  *
670  * \attention
671  * The external postprocessor is not allowed to add MIME related header fields
672  * like \c Content-Type and \c Content-Transfer-Encoding and must always create
673  * valid UTF-8 encoded data.
674  *
675  * The Unicode normalization and conversion to the target character set is done
676  * after the postprocessing.
677  *
678  * The caller is responsible to free the memory allocated for the result.
679  *
680  * \return
681  * - Pointer to postprocessed article.
682  * If the result is not equal to \e article , a new memory block was allocated
683  * - \c NULL on error
684  */
685 
686 const char* ext_pp_filter(const char* article)
687 {
688  const char* res = article;
689  const char* filter_pathname = config[CONF_PPROC].val.s;
690  int rv;
691  api_posix_struct_stat state;
692  int fd_in[2];
693  int fd_out[2];
694  api_posix_pid_t pid;
695  api_posix_pid_t rv2;
696  int error = 0;
697  int pushed = 0;
698  int finished = 0;
699  size_t len = strlen(article);
700  api_posix_ssize_t rv3;
701  size_t i = 0;
702  int status = -1;
703  char* buf = NULL;
704  size_t blen = 0;
705  size_t bi = 0;
706  char* p;
707 
708  if(API_POSIX_SIZE_MAX != len && strlen(filter_pathname))
709  {
710  res = NULL;
711  if(main_debug)
712  {
713  printf("%s: %sArticle post processor pathname: \x22%s\x22\n",
714  CFG_NAME, MAIN_ERR_PREFIX, filter_pathname);
715  }
716  rv = fu_check_file(filter_pathname, &state);
717  if(rv)
718  {
719  PRINT_ERROR("Article post processor not found");
720  }
721  else
722  {
723  if(API_POSIX_S_ISDIR(state.st_mode))
724  {
725  PRINT_ERROR("Directory specified for article postprocessor");
726  }
727  else
728  {
729  rv = api_posix_pipe(fd_in);
730  if(!rv)
731  {
732  rv = api_posix_pipe(fd_out);
733  if(!rv)
734  {
735  /* Spawn child process for filter */
736  pid = api_posix_fork();
737  if(!pid)
738  {
739  /* Executed by child process */
740  api_posix_close(fd_out[1]);
741  api_posix_close(fd_in[0]);
742  rv = api_posix_dup2(fd_out[0], API_POSIX_STDIN_FILENO);
743  if(-1 != rv)
744  {
745  rv = api_posix_dup2(fd_in[1], API_POSIX_STDOUT_FILENO);
746  if(-1 != rv)
747  {
749  if(!rv)
750  {
751  api_posix_execl(filter_pathname, filter_pathname,
752  (char*) NULL);
753  }
754  }
755  }
756  PRINT_ERROR("Executing article post processor failed");
757  exit(API_POSIX_EXIT_FAILURE);
758  }
759  else if(-1 != pid)
760  {
761  api_posix_close(fd_out[0]);
762  api_posix_close(fd_in[1]);
763  /* Set pipes to nonblocking mode */
764  rv = api_posix_fcntl(fd_out[1], API_POSIX_F_SETFL,
765  API_POSIX_O_NONBLOCK);
766  if(-1 != rv)
767  {
768  rv = api_posix_fcntl(fd_in[0], API_POSIX_F_SETFL,
769  API_POSIX_O_NONBLOCK);
770  }
771  if(-1 == rv)
772  {
773  PRINT_ERROR("Configuration of pipes failed");
774  error = 1;
775  }
776  else
777  {
778  while(!finished && !error)
779  {
780  /* Push article into postprocessor */
781  if(i < len)
782  {
783  rv3 = api_posix_write(fd_out[1],
784  (void*) &article[i],
785  len - i);
786  if(0 > rv3)
787  {
788  if(API_POSIX_EINTR == api_posix_errno)
789  {
790  continue;
791  }
792  if(API_POSIX_EAGAIN != api_posix_errno)
793  {
794  error = 1;
795  break;
796  }
797  }
798  else { i += (size_t) rv3; }
799  }
800  else
801  {
802  /* Transfer to stdin of postprocessor complete */
803  if(!pushed)
804  {
805  pushed = 1;
806  api_posix_close(fd_out[1]);
807  }
808  }
809  /* Read result */
810  while(!finished)
811  {
812  if((size_t) 4096 > blen - bi)
813  {
814  /* Allocate more memory for result */
815  if(!blen) { blen = 4096; }
816  else { blen *= 2; }
817  p = (char*) api_posix_realloc(buf, blen);
818  if(NULL == p) { error = 1; break; }
819  else { buf = p; }
820  }
821  /* Leave one byte left for NUL termination */
822  rv3 = api_posix_read(fd_in[0], (void*) &buf[bi],
823  (size_t) 4095);
824  if(0 > rv3)
825  {
826  if(API_POSIX_EINTR == api_posix_errno)
827  {
828  continue;
829  }
830  if(API_POSIX_EAGAIN != api_posix_errno)
831  {
832  error = 1;
833  }
834  break;
835  }
836  else if(0 < rv3) { bi += (size_t) rv3; }
837  /* Check whether operation is complete */
838  else
839  {
840  if(!pushed)
841  {
842  PRINT_ERROR("Postprocessor closed "
843  "stdout before reading data");
844  }
845  else
846  {
847  /* Yes => Terminate result string */
848  buf[bi] = 0;
849  finished = 1;
850  }
851  }
852  }
853  }
854  }
855  /* Terminate postprocessor child process after error */
856  if(error)
857  {
858  rv = api_posix_kill(pid, API_POSIX_SIGTERM);
859  if(rv)
860  {
861  PRINT_ERROR("Termination of child process "
862  "failed (bug)");
863  }
864  }
865  /* Get exit status of filter */
866  do { rv2 = api_posix_waitpid(pid, &status, 0); }
867  while((api_posix_pid_t) -1 == rv2
868  && API_POSIX_EINTR == api_posix_errno);
869  if(rv2 != pid)
870  {
871  PRINT_ERROR("Child process not found (bug)");
872  }
873  else
874  {
875  if(error || status)
876  {
877  PRINT_ERROR("Article post processor failed");
878  }
879  else
880  {
881  /* Success */
882  res = buf;
883  }
884  }
885  if(!pushed) { api_posix_close(fd_out[1]); }
886  api_posix_close(fd_in[0]);
887  }
888  }
889  }
890  }
891  }
892  }
893 
894  /* Check for error */
895  if(NULL == res) { api_posix_free((void*) buf); }
896 
897  return(res);
898 }
899 
900 
901 /* ========================================================================== */
902 /*! \brief Start external inews for article injection
903  *
904  * \param[in] article Article to inject in canonical form
905  *
906  * \return
907  * - Zero on success
908  * - -1 on error
909  */
910 
911 int ext_inews(const char* article)
912 {
913  int res = -1;
914  const char* inews_pathname = config[CONF_INEWS].val.s;
915  size_t len = 0;
916  api_posix_struct_stat state;
917  int rv;
918  int fd_out[2];
919  api_posix_pid_t pid;
920  api_posix_pid_t rv2;
921  int error = 0;
922  int pushed = 0;
923  api_posix_ssize_t rv3;
924  size_t i = 0;
925  int status = -1;
926 
927  if(NULL != article)
928  {
929  len = strlen(article);
930  if(len && strlen(inews_pathname))
931  {
932  if(main_debug)
933  {
934  printf("%s: %sDelegation to external inews: \x22%s\x22\n",
935  CFG_NAME, MAIN_ERR_PREFIX, inews_pathname);
936  }
937  rv = fu_check_file(inews_pathname, &state);
938  if(rv)
939  {
940  PRINT_ERROR("External inews not found");
941  }
942  else
943  {
944  if(API_POSIX_S_ISDIR(state.st_mode))
945  {
946  PRINT_ERROR("Directory specified for external inews");
947  }
948  else
949  {
950  /* Looks good */
951  res = 0;
952  }
953  }
954  }
955  }
956 
957  if(!res)
958  {
959  res = -1;
960  rv = api_posix_pipe(fd_out);
961  if(!rv)
962  {
963  /* Spawn child process for inews */
964  pid = api_posix_fork();
965  if(!pid)
966  {
967  /* Executed by child process */
968  api_posix_close(fd_out[1]);
969  rv = api_posix_dup2(fd_out[0], API_POSIX_STDIN_FILENO);
970  if(-1 != rv)
971  {
973  if(!rv)
974  {
975  api_posix_execl(inews_pathname, inews_pathname, (char*) NULL);
976  }
977  }
978  PRINT_ERROR("Executing external inews failed");
979  exit(API_POSIX_EXIT_FAILURE);
980  }
981  else if(-1 != pid)
982  {
983  api_posix_close(fd_out[0]);
984  /* Set pipe to nonblocking mode */
985  rv = api_posix_fcntl(fd_out[1], API_POSIX_F_SETFL,
986  API_POSIX_O_NONBLOCK);
987  if(-1 == rv)
988  {
989  PRINT_ERROR("Configuration of pipe failed");
990  error = 1;
991  }
992  else
993  {
994  while(!pushed && !error)
995  {
996  /* Push article into inews */
997  if(i < len)
998  {
999  rv3 = api_posix_write(fd_out[1], (void*) &article[i],
1000  len - i);
1001  if(0 > rv3)
1002  {
1003  if(API_POSIX_EINTR == api_posix_errno) { continue; }
1004  if(API_POSIX_EAGAIN != api_posix_errno)
1005  {
1006  error = 1;
1007  break;
1008  }
1009  }
1010  else { i += (size_t) rv3; }
1011  }
1012  else
1013  {
1014  /* Transfer to stdin of inews complete */
1015  if(!pushed)
1016  {
1017  api_posix_close(fd_out[1]);
1018  pushed = 1;
1019  }
1020  }
1021  }
1022  }
1023  /* Terminate inews child process after error */
1024  if(error)
1025  {
1026  rv = api_posix_kill(pid, API_POSIX_SIGTERM);
1027  if(rv)
1028  {
1029  PRINT_ERROR("Termination of child process failed (bug)");
1030  }
1031  }
1032  /* Get exit status of filter */
1033  do { rv2 = api_posix_waitpid(pid, &status, 0); }
1034  while((api_posix_pid_t) -1 == rv2
1035  && API_POSIX_EINTR == api_posix_errno);
1036  if(rv2 != pid)
1037  {
1038  PRINT_ERROR("Child process not found (bug)");
1039  }
1040  else
1041  {
1042  if(error || status)
1043  {
1044  PRINT_ERROR("External inews failed");
1045  }
1046  else
1047  {
1048  /* Success */
1049  printf("%s: %sExternal inews reported successful injection\n",
1050  CFG_NAME, MAIN_ERR_PREFIX);
1051  res = 0;
1052  }
1053  }
1054  if(!pushed) { api_posix_close(fd_out[1]); }
1055  api_posix_close(fd_out[0]);
1056  }
1057  }
1058  }
1059 
1060  return(res);
1061 }
1062 
1063 
1064 /* ========================================================================== */
1065 /*! \brief Download file from external source
1066  *
1067  * \param[in] lpn Local pathname where the file should be stored
1068  * \param[in] uri URI of file to download
1069  *
1070  * \note
1071  * The caller is responsible that write permission to \e lpn is granted.
1072  *
1073  * \return
1074  * - Zero on success (file is now stored at \e lpn )
1075  * - -1 on unspecified error
1076  * - -2 if URI scheme is not supported
1077  * - -3 if authority of URI is not reachable
1078  * - -4 if requested file not available via path of URI
1079  */
1080 
1081 int ext_download_file(const char* lpn, const char* uri)
1082 {
1083  /*
1084  * Note:
1085  * Currently the request is always delegated to the simple internal WWW
1086  * client and all URI schemes that are not "http" are rejected. URIs without
1087  * an authority field are rejected too.
1088  *
1089  * External download tools (like wget) can be hooked in here in the future.
1090  */
1091 
1092  int res = 0;
1093  const char scheme_http[] = "http";
1094  size_t len;
1095  size_t i = 0;
1096 
1097  /* Check (case insensitive) URI scheme, separator and double slash */
1098  len = strlen(scheme_http);
1099  while(i < len)
1100  {
1101  if(!uri[i]) { res = -2; break; }
1102  if((int) scheme_http[i] != tolower((int) uri[i])) { res = -2; break; }
1103  ++i;
1104  }
1105  if(!res) { if(':' != uri[i++]) { res = -2; } }
1106  if(-2 == res)
1107  {
1108  PRINT_ERROR("URI scheme for file download not supported");
1109  }
1110  if(!res) { if('/' != uri[i++]) { res = -1; } }
1111  if(!res) { if('/' != uri[i++]) { res = -1; } }
1112 
1113  /* Check URI encoding */
1114  if(!res)
1115  {
1116  if(enc_ascii_check_printable(uri)) { res = -1; }
1117  else
1118  {
1119  /*
1120  * Note: Check for HT and SP is not done yet
1121  * Check for single quote to allow passing of URI as shell parameter
1122  * Ensure that the URI has no query and fragment parts
1123  */
1124  len = strlen(uri);
1125  if(strcspn(uri, "\x09\x20'?#") != len) { res = -1; }
1126  }
1127  }
1128  if(-1 == res)
1129  {
1130  PRINT_ERROR("URI for file download has invalid format");
1131  }
1132 
1133  /* Delegate to internal WWW client */
1134  if(!res)
1135  {
1136  printf("%s: %sFile download delegation to %s for: %s\n",
1137  CFG_NAME, MAIN_ERR_PREFIX, lpn, uri);
1138  res = http_download_file(uri, lpn);
1139  }
1140 
1141  return(res);
1142 }
1143 
1144 
1145 /* ========================================================================== */
1146 /*! \brief Free an object allocated by external program delegation module
1147  *
1148  * Use this function to release dynamic memory that was allocated by the
1149  * external program delegation module.
1150  *
1151  * \param[in] p Pointer to object
1152  *
1153  * Release the memory for the object pointed to by \e p.
1154  *
1155  * \note
1156  * The pointer \e p is allowed to be \c NULL and no operation is performed in
1157  * this case.
1158  */
1159 
1160 void ext_free(void* p)
1161 {
1162  api_posix_free(p);
1163 }
1164 
1165 
1166 /*! @} */
1167 
1168 /* EOF */
CONF_EDITOR
Definition: conf.h:56
enc_ascii_check
int enc_ascii_check(const char *s)
Verify ASCII encoding.
Definition: encoding.c:3922
config
struct conf config[CONF_NUM]
Global configuration.
Definition: conf.c:63
conf_entry_val::s
char * s
Definition: conf.h:105
http_download_file
int http_download_file(const char *uri, const char *lpn)
Download file from WWW via HTTP.
Definition: http.c:887
ext_handler_uri
int ext_handler_uri(const char *uri, int *invalid)
Start external handler for URI.
Definition: extutils.c:391
MAIN_ERR_PREFIX
#define MAIN_ERR_PREFIX
Message prefix for EXTUTILS module.
Definition: extutils.c:47
main_debug
int main_debug
Enable additional debug output if nonzero.
Definition: main.cxx:64
fu_check_file
int fu_check_file(const char *pathname, api_posix_struct_stat *state)
Check whether file exist.
Definition: fileutils.c:214
enc_ascii_check_printable
int enc_ascii_check_printable(const char *s)
Check for printable ASCII characters.
Definition: encoding.c:4000
CONF_INEWS
Definition: conf.h:58
ext_editor_status
int ext_editor_status(long int editor_pid)
Poll status of external editor.
Definition: extutils.c:607
ext_pp_filter
const char * ext_pp_filter(const char *article)
External post processing filter for outgoing articles.
Definition: extutils.c:686
PRINT_ERROR
#define PRINT_ERROR(s)
Prepend module prefix and print error message.
Definition: main.h:19
ext_download_file
int ext_download_file(const char *lpn, const char *uri)
Download file from external source.
Definition: extutils.c:1081
ext_inews
int ext_inews(const char *article)
Start external inews for article injection.
Definition: extutils.c:911
sighandler_exec_prepare
int sighandler_exec_prepare(void)
Prepare for exec()
Definition: sighandler.c:126
conf::val
union conf_entry_val val
Definition: conf.h:113
ext_editor_terminate
void ext_editor_terminate(long int editor_pid)
Terminate external editor.
Definition: extutils.c:653
ext_handler_email
int ext_handler_email(const char *recipient, const char *subject, const char *body)
Send e-mail to single recipient.
Definition: extutils.c:170
ext_editor
int ext_editor(const char *tfpn, int async,...)
Start external editor for article composition.
Definition: extutils.c:517
CONF_PPROC
Definition: conf.h:57
ext_free
void ext_free(void *p)
Free an object allocated by external program delegation module.
Definition: extutils.c:1160

Generated at 2026-01-27 using  doxygen