extutils.c
Go to the documentation of this file.
1 /* ========================================================================== */
2 /*! \file
3  * \brief External program delegation functions
4  *
5  * Copyright (c) 2012-2022 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*) posix_malloc(++len); /* One additional byte for NUL */
89  if(NULL != res) { strncpy(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(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*) posix_malloc(++len); /* One additional 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*) posix_malloc(++len); /* One additional 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(POSIX_SIZE_MAX - len_tmp < x) { error = 1; }
278  else
279  {
280  if(POSIX_SIZE_MAX - len < len_tmp + x) { error = 1; }
281  else
282  {
283  len += len_tmp + x;
284  tmp = (char*) 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(POSIX_SIZE_MAX - len_tmp < x) { error = 1; }
303  else
304  {
305  if(POSIX_SIZE_MAX - len < len_tmp + x) { error = 1; }
306  else
307  {
308  len += len_tmp + x;
309  tmp = (char*) 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(POSIX_SIZE_MAX - len_tmp < (size_t) 3) { error = 1; }
327  else
328  {
329  if(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*) 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 = posix_popen(buf, "w");
352  if(NULL != fs) { res = posix_pclose(fs); }
353  }
354 
355  /* Release memory */
356  posix_free((void*) body);
357  posix_free((void*) subject);
358  posix_free((void*) recipient);
359  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*) posix_malloc(++len); /* One additional 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(POSIX_SIZE_MAX - len_tmp < (size_t) 5) { error = 1; }
453  else
454  {
455  if(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*) posix_realloc(buf, len);
461  if(NULL == tmp) { error = 1; }
462  else
463  {
464  buf = tmp;
465  strcat(buf, " \x27");
466  strncat(buf, uri, len_tmp);
467  strcat(buf, "\x27 &");
468  }
469  }
470  }
471  }
472 
473  /* Spawn new process for URI handling */
474  if(!error)
475  {
476  fs = posix_popen(buf, "w");
477  if(NULL != fs) { res = posix_pclose(fs); }
478  }
479 
480  /* Release memory */
481  posix_free((void*) uri);
482  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  posix_pid_t pid;
524  int rv;
525  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 = posix_fork();
537  if(!pid)
538  {
539  /* Executed by child process */
541  if(!rv) { posix_execlp(epn, epn, tfpn, (char*) NULL); }
542  PRINT_ERROR("Executing external editor failed");
543  exit(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 = posix_waitpid(pid, &status, 0); }
559  while((posix_pid_t) -1 == rv2 && POSIX_EINTR == posix_errno);
560  if(rv2 != pid)
561  {
562  PRINT_ERROR("Child process not found (bug)");
563  res = -1;
564  }
565  else
566  {
567  if(!status) { res = 0; }
568  else
569  {
570  PRINT_ERROR("External editor reported error");
571  if(!POSIX_WIFEXITED(status)) { res = POSIX_EXIT_FAILURE; }
572  else { res = POSIX_WEXITSTATUS(status); }
573  }
574  }
575  }
576  }
577  }
578 
579  return(res);
580 }
581 
582 
583 /* ========================================================================== */
584 /*! \brief Poll status of external editor
585  *
586  * \param[in] editor_pid PID of external editor
587  *
588  * \attention
589  * The value \e editor_pid must correspond to a value returned by
590  * \ref ext_editor() in asynchronous mode.
591  *
592  * \attention
593  * It is not allowed to call this function again for the same value of
594  * \e editor_pid after either success or error was returned.
595  *
596  * \return
597  * - 0 Success
598  * - -1 if external editor is still running
599  * - Other negative value on local error
600  * - Positive value if external editor reported error
601  */
602 
603 int ext_editor_status(long int editor_pid)
604 {
605  int res = -1;
606  posix_pid_t pid = (posix_pid_t) editor_pid;
607  posix_pid_t rv2;
608  int status = -1;
609 
610  /* Get exit status of editor */
611  do { rv2 = posix_waitpid(pid, &status, POSIX_WNOHANG); }
612  while((posix_pid_t) -1 == rv2 && POSIX_EINTR == posix_errno);
613  if(rv2)
614  {
615  if(rv2 != pid)
616  {
617  PRINT_ERROR("Child process not found (bug)");
618  res = -2;
619  }
620  else
621  {
622  if(!status) { res = 0; }
623  else
624  {
625  PRINT_ERROR("External editor reported error");
626  if(!POSIX_WIFEXITED(status)) { res = POSIX_EXIT_FAILURE; }
627  else { res = POSIX_WEXITSTATUS(status); }
628  }
629  }
630  }
631 
632  return(res);
633 }
634 
635 
636 /* ========================================================================== */
637 /*! \brief Terminate external editor
638  *
639  * \param[in] editor_pid PID of external editor
640  *
641  * \attention
642  * The value \e editor_pid must correspond to a value returned by
643  * \ref ext_editor() in asynchronous mode.
644  *
645  * \attention
646  * It is not allowed to call this function multiple times for the same editor.
647  */
648 
649 void ext_editor_terminate(long int editor_pid)
650 {
651  posix_kill((posix_pid_t) editor_pid, POSIX_SIGTERM);
652 }
653 
654 
655 /* ========================================================================== */
656 /*! \brief External post processing filter for outgoing articles
657  *
658  * \param[in] article Pointer to article (in canonical form)
659  *
660  * \note
661  * The body of \e article is still in Unicode and the MIME content type is not
662  * added to the header yet.
663  *
664  * The pathname of the external filter is taken from the configfile.
665  *
666  * \attention
667  * The external postprocessor is not allowed to add MIME related header fields
668  * like \c Content-Type and \c Content-Transfer-Encoding and must always create
669  * valid UTF-8 encoded data.
670  *
671  * The Unicode normalization and conversion to the target character set is done
672  * after the postprocessing.
673  *
674  * The caller is responsible to free the memory allocated for the result.
675  *
676  * \return
677  * - Pointer to postprocessed article.
678  * If the result is not equal to \e article , a new memory block was allocated
679  * - \c NULL on error
680  */
681 
682 const char* ext_pp_filter(const char* article)
683 {
684  const char* res = article;
685  const char* filter_pathname = config[CONF_PPROC].val.s;
686  int rv;
687  struct_posix_stat state;
688  int fd_in[2];
689  int fd_out[2];
690  posix_pid_t pid;
691  posix_pid_t rv2;
692  int error = 0;
693  int pushed = 0;
694  int finished = 0;
695  size_t len = strlen(article);
696  posix_ssize_t rv3;
697  size_t i = 0;
698  int status = -1;
699  char* buf = NULL;
700  size_t blen = 0;
701  size_t bi = 0;
702  char* p;
703 
704  if(POSIX_SIZE_MAX != len && strlen(filter_pathname))
705  {
706  res = NULL;
707  if(main_debug)
708  {
709  printf("%s: %sArticle post processor pathname: \x22%s\x22\n",
710  CFG_NAME, MAIN_ERR_PREFIX, filter_pathname);
711  }
712  rv = fu_check_file(filter_pathname, &state);
713  if(rv)
714  {
715  PRINT_ERROR("Article post processor not found");
716  }
717  else
718  {
719  if(POSIX_S_ISDIR(state.st_mode))
720  {
721  PRINT_ERROR("Directory specified for article postprocessor");
722  }
723  else
724  {
725  rv = posix_pipe(fd_in);
726  if(!rv)
727  {
728  rv = posix_pipe(fd_out);
729  if(!rv)
730  {
731  /* Spawn child process for filter */
732  pid = posix_fork();
733  if(!pid)
734  {
735  /* Executed by child process */
736  posix_close(fd_out[1]);
737  posix_close(fd_in[0]);
738  rv = posix_dup2(fd_out[0], POSIX_STDIN_FILENO);
739  if(-1 != rv)
740  {
741  rv = posix_dup2(fd_in[1], POSIX_STDOUT_FILENO);
742  if(-1 != rv)
743  {
745  if(!rv)
746  {
747  posix_execl(filter_pathname, filter_pathname,
748  (char*) NULL);
749  }
750  }
751  }
752  PRINT_ERROR("Executing article post processor failed");
753  exit(POSIX_EXIT_FAILURE);
754  }
755  else if(-1 != pid)
756  {
757  posix_close(fd_out[0]);
758  posix_close(fd_in[1]);
759  /* Set pipes to nonblocking mode */
760  rv = posix_fcntl(fd_out[1], POSIX_F_SETFL,
761  POSIX_O_NONBLOCK);
762  if(-1 != rv)
763  {
764  rv = posix_fcntl(fd_in[0], POSIX_F_SETFL,
765  POSIX_O_NONBLOCK);
766  }
767  if(-1 == rv)
768  {
769  PRINT_ERROR("Configuration of pipes failed");
770  error = 1;
771  }
772  else
773  {
774  while(!finished && !error)
775  {
776  /* Push article into postprocessor */
777  if(i < len)
778  {
779  rv3 = posix_write(fd_out[1], (void*) &article[i],
780  len - i);
781  if(0 > rv3)
782  {
783  if(POSIX_EINTR == posix_errno) { continue; }
784  if(POSIX_EAGAIN != posix_errno)
785  {
786  error = 1;
787  break;
788  }
789  }
790  else { i += (size_t) rv3; }
791  }
792  else
793  {
794  /* Transfer to stdin of postprocessor complete */
795  if(!pushed)
796  {
797  pushed = 1;
798  posix_close(fd_out[1]);
799  }
800  }
801  /* Read result */
802  while(!finished)
803  {
804  if((size_t) 4096 > blen - bi)
805  {
806  /* Allocate more memory for result */
807  if(!blen) { blen = 4096; }
808  else { blen *= 2; }
809  p = (char*) posix_realloc(buf, blen);
810  if(NULL == p) { error = 1; break; }
811  else { buf = p; }
812  }
813  /* Leave one byte left for NUL termination */
814  rv3 = posix_read(fd_in[0], (void*) &buf[bi],
815  (size_t) 4095);
816  if(0 > rv3)
817  {
818  if(POSIX_EINTR == posix_errno) { continue; }
819  if(POSIX_EAGAIN != posix_errno) { error = 1; }
820  break;
821  }
822  else if(0 < rv3) { bi += (size_t) rv3; }
823  /* Check whether operation is complete */
824  else
825  {
826  if(!pushed)
827  {
828  PRINT_ERROR("Postprocessor closed "
829  "stdout before reading data");
830  }
831  else
832  {
833  /* Yes => Terminate result string */
834  buf[bi] = 0;
835  finished = 1;
836  }
837  }
838  }
839  }
840  }
841  /* Terminate postprocessor child process after error */
842  if(error)
843  {
844  rv = posix_kill(pid, POSIX_SIGTERM);
845  if(rv)
846  {
847  PRINT_ERROR("Termination of child process "
848  "failed (bug)");
849  }
850  }
851  /* Get exit status of filter */
852  do { rv2 = posix_waitpid(pid, &status, 0); }
853  while((posix_pid_t) -1 == rv2
854  && POSIX_EINTR == posix_errno);
855  if(rv2 != pid)
856  {
857  PRINT_ERROR("Child process not found (bug)");
858  }
859  else
860  {
861  if(error || status)
862  {
863  PRINT_ERROR("Article post processor failed");
864  }
865  else
866  {
867  /* Success */
868  res = buf;
869  }
870  }
871  if(!pushed) { posix_close(fd_out[1]); }
872  posix_close(fd_in[0]);
873  }
874  }
875  }
876  }
877  }
878  }
879 
880  /* Check for error */
881  if(NULL == res) { posix_free((void*) buf); }
882 
883  return(res);
884 }
885 
886 
887 /* ========================================================================== */
888 /*! \brief Start external inews for article injection
889  *
890  * \param[in] article Article to inject in canonical form
891  *
892  * \return
893  * - Zero on success
894  * - -1 on error
895  */
896 
897 int ext_inews(const char* article)
898 {
899  int res = -1;
900  const char* inews_pathname = config[CONF_INEWS].val.s;
901  size_t len = 0;
902  struct_posix_stat state;
903  int rv;
904  int fd_out[2];
905  posix_pid_t pid;
906  posix_pid_t rv2;
907  int error = 0;
908  int pushed = 0;
909  posix_ssize_t rv3;
910  size_t i = 0;
911  int status = -1;
912 
913  if(NULL != article)
914  {
915  len = strlen(article);
916  if(len && strlen(inews_pathname))
917  {
918  if(main_debug)
919  {
920  printf("%s: %sDelegation to external inews: \x22%s\x22\n",
921  CFG_NAME, MAIN_ERR_PREFIX, inews_pathname);
922  }
923  rv = fu_check_file(inews_pathname, &state);
924  if(rv)
925  {
926  PRINT_ERROR("External inews not found");
927  }
928  else
929  {
930  if(POSIX_S_ISDIR(state.st_mode))
931  {
932  PRINT_ERROR("Directory specified for external inews");
933  }
934  else
935  {
936  /* Looks good */
937  res = 0;
938  }
939  }
940  }
941  }
942 
943  if(!res)
944  {
945  res = -1;
946  rv = posix_pipe(fd_out);
947  if(!rv)
948  {
949  /* Spawn child process for inews */
950  pid = posix_fork();
951  if(!pid)
952  {
953  /* Executed by child process */
954  posix_close(fd_out[1]);
955  rv = posix_dup2(fd_out[0], POSIX_STDIN_FILENO);
956  if(-1 != rv)
957  {
959  if(!rv)
960  {
961  posix_execl(inews_pathname, inews_pathname, (char*) NULL);
962  }
963  }
964  PRINT_ERROR("Executing external inews failed");
965  exit(POSIX_EXIT_FAILURE);
966  }
967  else if(-1 != pid)
968  {
969  posix_close(fd_out[0]);
970  /* Set pipe to nonblocking mode */
971  rv = posix_fcntl(fd_out[1], POSIX_F_SETFL, POSIX_O_NONBLOCK);
972  if(-1 == rv)
973  {
974  PRINT_ERROR("Configuration of pipe failed");
975  error = 1;
976  }
977  else
978  {
979  while(!pushed && !error)
980  {
981  /* Push article into inews */
982  if(i < len)
983  {
984  rv3 = posix_write(fd_out[1], (void*) &article[i], len - i);
985  if(0 > rv3)
986  {
987  if(POSIX_EINTR == posix_errno) { continue; }
988  if(POSIX_EAGAIN != posix_errno)
989  {
990  error = 1;
991  break;
992  }
993  }
994  else { i += (size_t) rv3; }
995  }
996  else
997  {
998  /* Transfer to stdin of inews complete */
999  if(!pushed)
1000  {
1001  posix_close(fd_out[1]);
1002  pushed = 1;
1003  }
1004  }
1005  }
1006  }
1007  /* Terminate inews child process after error */
1008  if(error)
1009  {
1010  rv = posix_kill(pid, POSIX_SIGTERM);
1011  if(rv)
1012  {
1013  PRINT_ERROR("Termination of child process failed (bug)");
1014  }
1015  }
1016  /* Get exit status of filter */
1017  do { rv2 = posix_waitpid(pid, &status, 0); }
1018  while((posix_pid_t) -1 == rv2 && POSIX_EINTR == posix_errno);
1019  if(rv2 != pid)
1020  {
1021  PRINT_ERROR("Child process not found (bug)");
1022  }
1023  else
1024  {
1025  if(error || status)
1026  {
1027  PRINT_ERROR("External inews failed");
1028  }
1029  else
1030  {
1031  /* Success */
1032  printf("%s: %sExternal inews reported successful injection\n",
1033  CFG_NAME, MAIN_ERR_PREFIX);
1034  res = 0;
1035  }
1036  }
1037  if(!pushed) { posix_close(fd_out[1]); }
1038  posix_close(fd_out[0]);
1039  }
1040  }
1041  }
1042 
1043  return(res);
1044 }
1045 
1046 
1047 /* ========================================================================== */
1048 /*! \brief Download file from external source
1049  *
1050  * \param[in] lpn Local pathname where the file should be stored
1051  * \param[in] uri URI of file to download
1052  *
1053  * \note
1054  * The caller is responsible that write permission to \e lpn is granted.
1055  *
1056  * \return
1057  * - Zero on success (file is now stored at \e lpn )
1058  * - -1 on unspecified error
1059  * - -2 if URI scheme is not supported
1060  * - -3 if authority of URI is not reachable
1061  * - -4 if requested file not available via path of URI
1062  */
1063 
1064 int ext_download_file(const char* lpn, const char* uri)
1065 {
1066  /*
1067  * Note:
1068  * Currently the request is always delegated to the simple internal WWW
1069  * client and all URI schemes that are not "http" are rejected. URIs without
1070  * an authority field are rejected too.
1071  *
1072  * External download tools (like wget) can be hooked in here in the future.
1073  */
1074 
1075  int res = 0;
1076  const char scheme_http[] = "http";
1077  size_t len;
1078  size_t i = 0;
1079 
1080  /* Check (case insensitive) URI scheme, separator and double slash */
1081  len = strlen(scheme_http);
1082  while(i < len)
1083  {
1084  if(!uri[i]) { res = -2; break; }
1085  if((int) scheme_http[i] != tolower((int) uri[i])) { res = -2; break; }
1086  ++i;
1087  }
1088  if(!res) { if(':' != uri[i++]) { res = -2; } }
1089  if(-2 == res)
1090  {
1091  PRINT_ERROR("URI scheme for file download not supported");
1092  }
1093  if(!res) { if('/' != uri[i++]) { res = -1; } }
1094  if(!res) { if('/' != uri[i++]) { res = -1; } }
1095 
1096  /* Check URI encoding */
1097  if(!res)
1098  {
1099  if(enc_ascii_check_printable(uri)) { res = -1; }
1100  else
1101  {
1102  /*
1103  * Note: Check for HT and SP is not done yet
1104  * Check for single quote to allow passing of URI as shell parameter
1105  * Ensure that the URI has no query and fragment parts
1106  */
1107  len = strlen(uri);
1108  if(strcspn(uri, "\x09\x20'?#") != len) { res = -1; }
1109  }
1110  }
1111  if(-1 == res)
1112  {
1113  PRINT_ERROR("URI for file download has invalid format");
1114  }
1115 
1116  /* Delegate to internal WWW client */
1117  if(!res)
1118  {
1119  printf("%s: %sFile download delegation to %s for: %s\n",
1120  CFG_NAME, MAIN_ERR_PREFIX, lpn, uri);
1121  res = http_download_file(uri, lpn);
1122  }
1123 
1124  return(res);
1125 }
1126 
1127 
1128 /* ========================================================================== */
1129 /*! \brief Free an object allocated by external program delegation module
1130  *
1131  * Use this function to release dynamic memory that was allocated by the
1132  * external program delegation module.
1133  *
1134  * \param[in] p Pointer to object
1135  *
1136  * Release the memory for the object pointed to by \e p.
1137  *
1138  * \note
1139  * The pointer \e p is allowed to be \c NULL and no operation is performed in
1140  * this case.
1141  */
1142 
1143 void ext_free(void* p)
1144 {
1145  posix_free(p);
1146 }
1147 
1148 
1149 /*! @} */
1150 
1151 /* EOF */
CONF_EDITOR
Definition: conf.h:56
enc_ascii_check
int enc_ascii_check(const char *s)
Verify ASCII encoding.
Definition: encoding.c:4944
config
struct conf config[CONF_NUM]
Global configuration.
Definition: conf.c:63
conf_entry_val::s
char * s
Definition: conf.h:103
http_download_file
int http_download_file(const char *uri, const char *lpn)
Download file from WWW via HTTP.
Definition: http.c:880
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
enc_ascii_check_printable
int enc_ascii_check_printable(const char *s)
Check for printable ASCII characters.
Definition: encoding.c:5022
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:603
ext_pp_filter
const char * ext_pp_filter(const char *article)
External post processing filter for outgoing articles.
Definition: extutils.c:682
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:1064
ext_inews
int ext_inews(const char *article)
Start external inews for article injection.
Definition: extutils.c:897
sighandler_exec_prepare
int sighandler_exec_prepare(void)
Prepare for exec()
Definition: sighandler.c:119
conf::val
union conf_entry_val val
Definition: conf.h:111
ext_editor_terminate
void ext_editor_terminate(long int editor_pid)
Terminate external editor.
Definition: extutils.c:649
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
fu_check_file
int fu_check_file(const char *pathname, struct_posix_stat *state)
Check whether file exist.
Definition: fileutils.c:211
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:1143

Generated at 2024-04-27 using  doxygen