1 /*
2 **  OSSP l2 - Flexible Logging
3 **  Copyright (c) 2001-2005 Cable & Wireless <http://www.cw.com/>
4 **  Copyright (c) 2001-2005 The OSSP Project <http://www.ossp.org/>
5 **  Copyright (c) 2001-2005 Ralf S. Engelschall <rse@engelschall.com>
6 **
7 **  This file is part of OSSP l2, a flexible logging library which
8 **  can be found at http://www.ossp.org/pkg/lib/l2/.
9 **
10 **  Permission to use, copy, modify, and distribute this software for
11 **  any purpose with or without fee is hereby granted, provided that
12 **  the above copyright notice and this permission notice appear in all
13 **  copies.
14 **
15 **  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
16 **  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
17 **  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 **  IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
19 **  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 **  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21 **  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
22 **  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 **  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 **  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
25 **  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 **  SUCH DAMAGE.
27 **
28 **  l2_ch_pipe.c: pipe channel implementation
29 */
30 
31 #include "l2.h"
32 #include "l2_p.h"              /* for TRACE() */
33 
34 #include <unistd.h>
35 #include <signal.h>
36 #include <sys/wait.h>
37 #include <fcntl.h>
38 
39 #define L2_PIPE_EXECMODE_DIRECT    1 /* direct command execution             */
40 #define L2_PIPE_EXECMODE_SHELL     2 /* shell  command execution             */
41 #define L2_PIPE_RUNTIME_CONTINU    3 /* continuous pipe command processing   */
42 #define L2_PIPE_RUNTIME_ONESHOT    4 /* oneshot  pipe command processing     */
43 #define L2_PIPE_WRITEFAIL          6 /* how long before failure occurs       */
44 #define L2_PIPE_MAXARGS          256 /* how many args can piped command have */
45 
46 /* declare private channel configuration */
47 typedef struct {
48     pid_t            Pid;        /* process id of child command          */
49     int              iWritefail; /* counter to failed write() operations */
50     int              piFd[2];    /* pipe file descriptor                 */
51     int              iNulldev;   /* null device file descriptor          */
52     int              iMode;      /* execution mode direct or shell       */
53     int              iRtme;      /* runtime mode continuous or oneshot   */
54     char            *szCmdpath;  /* path to command and arguments        */
55     struct sigaction sigchld;    /* initial state of chld signal handler */
56     struct sigaction sigpipe;    /* initial state of pipe signal handler */
57 } l2_ch_pipe_t;
58 
catchsignal(int sig,...)59 static void catchsignal(int sig, ...)
60 {
61     pid_t Pid;         /* for wait() */
62     int   iStatus = 0; /* for wait() */
63 
64     if (sig == SIGCHLD) {
65         TRACE("SIGCHLD caught");
66         Pid = waitpid((pid_t)-1, &iStatus, WUNTRACED | WNOHANG);
67         if (WIFEXITED(iStatus))
68             TRACE("EXITED child");     /* child finished and returned       */
69         else if (WIFSIGNALED(iStatus))
70             TRACE("SIGNALED child");   /* child finished due to a signal    */
71         else if (WIFSTOPPED(iStatus))
72             TRACE("STOPPED child");    /* child stopped due to a signal     */
73         else
74             TRACE("SIGNAL Unknown");   /* child stopped due to a signal     */
75     }
76     else if (sig == SIGPIPE);          /* noop for now                      */
77 }
78 
79 /*
80  * The lifecycle of the command executed by the pipe channel handler
81  * depends on the runtime option selected during configuration. The
82  * option 'continuous' describes subsequent behavior which rebuilds an
83  * (un)intentionally severed child-controlled pipe. In this case, when
84  * the pipe handler encounters a failed write operation, it will attempt
85  * the reconnection a user-defined number of times until failure is assumed.
86  *
87  * The option 'oneshot' describes subsequent behavior which also rebuilds
88  * such a severed pipe. The difference is that the pipe handler will not
89  * spawn the child process until writing actually occurs. The channel will
90  * also block until the child process finishes and terminates on its own.
91  *
92  * The lifecycle of a pipe command process is illustrated in the
93  * following figure:
94  *
95  * -----BEGIN EMBEDDED OBJECT-----
96  * Content-editor: xfig %s
97  * Content-encoding: gzip:9 base64
98  * Content-type: application/fig
99  * Content-viewer: xfig %s
100  * Description: L2 Pipe Channel Command Lifecycle
101  * Filename: l2_ch_pipe.fig
102  * Last-modified: 2000-09-28/14:40
103  * Name: l2_ch_pipe
104  * Version: eo/1.0
105  * H4sIAIW5uTsCA61ZUW/cNgx+3v0KAX3YCjSBJEuy/VxsQ4EMG9bubS+Oo+SM+uyb
106  * 7WuWfz+Ssi352iTSrW3jir7jJ0qk+FHMm18+/Mqya7m7qbq7sa6OdvfedpMddr/Z
107  * aWjq3Y2dQGJsJzi/5nz3sekeWru7kjshOWdyx1km2Zua/uwMk0ZqprjmTPAi10yD
108  * vBMsY/ACfjjT+JDwADR6h//DmxweWpWcGYn/vCxzAyPN8xgYmJS7b884wQsYEpJU
109  * MUiZ2Rrk5cwkGKTVFsbLWiXAKL2F8bLSCTC53MJ4OZcJMIXYwni5EAkwJa1MqAXG
110  * yyU3ONJxMOUZzCqXZQKMybYwgZwtMJIgFPzkDuZqVmeLgNBy90MQvz5wQqct2KrU
111  * 4Qg/Be0wSlYrch2OZq/xcISfgnZulm/SaHYMD0f0aaH1ut9eu1Tr22AjF+35PKGZ
112  * VwbOe1nCIRcKDr2QGQwlYO6U32fUWIOYiQy+V9A55TjUoPFXdwcJpuuHQ9Wyuhnq
113  * 02Gcqq6247u/ORcvg0nhwRC3YjeS/dEcLXu/r7rOtuyxmfZssG1T3TZtMz3FYGYL
114  * puSwsOk0dPaO9R1gtS1YOrHOwoupZ1P12bKqew2UA5Is9QIq4Tt1Pwy2npovoA//
115  * ATjAHaqmm+CHNdPI6n3T3sVYm6/ACoZ1fzhALmfHoYctHK/Zz/9Wh2Nr2bjvH7tr
116  * AvSeUxK1pcFMDZH2iufyfNkYpeBD57nxaOvm/7lOGXh8F9fxYtkMhcn+zHWDtWDf
117  * bduMe9riI8wWAZrpBVQj/t1pABIEe+/b6hHQH4dmsqw/2qEiR96dLDgzAlcXywbo
118  * TGDs1m0/AiB5njXd8TSRic/7kEIpk3j6BCUcPH0uSQlWYJISzyUpzFFyyQ4zjopQ
119  * Fk4ZB+Ia0yZ+CiFE1UGIOVuEmQJB8S9UCh5VPmsSpRynTjnJLdVQjqFgx8JCFrDU
120  * 2MKCHkFhMT8MPkRCYYHf3hQW9MIVFjLPVDSVBwZ5mag81iCiJwijDV05GelKZCae
121  * ygNrvExUHmsNkVwA42WqlmJhiO2CRXmZqqXYRREZBtZ4mcqcWGuIFQNrvEz1Saw1
122  * RJ4hzCpTfUIwUYWFEEthMe+sD5zNaI6CzQg/Be3Qvd6KzYg+XaKbz6fPpSlT4MEE
123  * itegrvRr+d0s6c0g1XzaW3YagTAO1RNk4n9OzQBZs7NXkNSm2FQMs86Y5NNbu6++
124  * NP1wzT4Age4bYM1qtO9gaCGLEov8OEbASr7CYilTjWMPnDZhLp7pdKX+8Vg9dhGQ
125  * rlgiSExXt21ffx7ZqZuadjYPk/zM0gzuWIemgxmjrDWLteTE+6E/EJ0BN7C2f2jq
126  * bxLGKxynVnOxZnR2IrMh1d3ah6aDUuKT31ZcQ3dGRFg4urQt81wn8pCL6xnm+9CQ
127  * i2SxHJhUGqIj4dRxOC8Ua6wZGCsvWcrNORBmu7eQDhT8SI2PAgi/Hiy4+dwj52p4
128  * bCHUMyqpSa3v7puH0/CqJsyTgabKzKwJhUkXqURlCClRTROpZeSiRfVL5NIoJ1y0
129  * tLy8YGlFecnSynyZKnZpGqsUbhZv30HBOfRPwUHBQNAaq2lBd+VcRMePNvkl8ePU
130  * Lokf0kyNH1JK2WRyTbIWucYtLdk1pBa6JjLIk42kIE/WogA/03IsTMaXBZX7eJWH
131  * oojwihBPXOucFwEe0ZvBEdx+Aiue16Kajec4MHzhwG/rbazHykAInA5NY8MJWLk5
132  * fL3u4mvnoCYur8jATaDYAfG8pocHA3mL9ARsCBB0ylzknvO5Cq8nA72Cu9yvS+oI
133  * wWTLdfV3qGE+Yg1z09zb+qluzz2m+OIwzAmvTJJht4AmAXJf5/iTbsFA6Osc7CfX
134  * Mnkb4c41CPDMvBADz/iSMtklvpS5usiX0ohkX4qsfN6XL0Q4VrqbCPd+y0S5OA5P
135  * Y6z1dGkJrY9wTIbrSD6dWZFfdjopuVziUSWKlBOTcTUHM11WXormweIV4G2KMUSj
136  * oTGeYSmLOIYtNI9mWMpa6Qzr1C5hWNJMZVhSSq7QnJGpFdpFSyMCS10alQHJS6My
137  * IGlpvgwgta8rNKz9DV+vDhhKiXeZQhZ8wcm+02WGMGeLYJh6mTFlNt/vcbS01GBv
138  * 3J2eF1hOZHl8S43sCVpqbs3YUitkmdBSw29vWmr0wrXUikzE/64uMMjL1H2KNYia
139  * JwGMl6kzFwtD+T+A8TI1sWJhqMERwHiZmlixMNS0CmC8TE2saJjyDGaVqYkVC0MN
140  * iwDGy9RujIWhlhaFdND8dDI2P02pYltq6+/q5nDxHvdO24zcLPl8pECbxvOG8nDk
141  * jpzS4cid4jnOCzqH/wEp6AKCgx8AAA==
142  * -----END EMBEDDED OBJECT-----
143  */
144 
145 /* create channel */
hook_create(l2_context_t * ctx,l2_channel_t * ch)146 static l2_result_t hook_create(l2_context_t *ctx, l2_channel_t *ch)
147 {
148     l2_ch_pipe_t *cfg;
149 
150     /* allocate private channel configuration */
151     if ((cfg = (l2_ch_pipe_t *)malloc(sizeof(l2_ch_pipe_t))) == NULL)
152         return L2_ERR_ARG;
153 
154     /* initialize configuration with reasonable defaults */
155     cfg->Pid        = -1;
156     cfg->iWritefail =  0;
157     cfg->piFd[0]    = -1;
158     cfg->piFd[1]    = -1;
159     cfg->iNulldev   = -1;
160     cfg->iMode      = -1;
161     cfg->iRtme      = -1;
162     cfg->szCmdpath  = NULL;
163     memset(&cfg->sigchld, 0, sizeof(cfg->sigchld));
164     memset(&cfg->sigpipe, 0, sizeof(cfg->sigpipe));
165 
166     /* link private channel configuration into channel context */
167     ctx->vp = cfg;
168 
169     return L2_OK;
170 }
171 
172 /* configure channel */
hook_configure(l2_context_t * ctx,l2_channel_t * ch,const char * fmt,va_list * ap)173 static l2_result_t hook_configure(l2_context_t *ctx, l2_channel_t *ch, const char *fmt, va_list *ap)
174 {
175     l2_ch_pipe_t *cfg = (l2_ch_pipe_t *)ctx->vp;
176     l2_param_t pa[4];
177     l2_result_t rv;
178     char *szMode = NULL;
179     char *szRel  = NULL;
180     l2_env_t *env;
181 
182     /* feed and call generic parameter parsing engine */
183     L2_PARAM_SET(pa[0], execmode, STR, &szMode); /* mode direct or shell  */
184     L2_PARAM_SET(pa[1], runtime,  STR, &szRel);  /* continuous or oneshot */
185     L2_PARAM_SET(pa[2], path,     STR, &cfg->szCmdpath); /* path of cmd   */
186     L2_PARAM_END(pa[3]);
187     l2_channel_env(ch, &env);
188     if ((rv = l2_util_setparams(env, pa, fmt, ap)) != L2_OK)
189         return rv;
190 
191     if (szMode != NULL) {
192         if (strcmp(szMode, "direct") == 0)
193             cfg->iMode = L2_PIPE_EXECMODE_DIRECT;
194         else if (strcmp(szMode, "shell") == 0)
195             cfg->iMode = L2_PIPE_EXECMODE_SHELL;
196         else
197             return L2_ERR_ARG;
198         free(szMode);
199     }
200 
201     if (szRel != NULL) {
202         if (strncmp(szRel, "continuous", strlen("cont")) == 0)
203             cfg->iRtme = L2_PIPE_RUNTIME_CONTINU;
204         else if (strncmp(szMode, "oneshot", strlen("one")) == 0)
205             cfg->iRtme = L2_PIPE_RUNTIME_ONESHOT;
206         else
207             return L2_ERR_ARG;
208         free(szRel);
209     }
210 
211     return L2_OK;
212 }
213 
214 /**********************************************************
215  * parse_cmdpath: Helper method to spawn_command          *
216  *   Parses szBuf into an argv-style string vector szArgs *
217  **********************************************************/
parse_cmdpath(char * szBuf,char * szArgs[])218 static l2_result_t parse_cmdpath(char *szBuf, char *szArgs[]) {
219     int iCnt = 0;
220 
221     if (szBuf == NULL)     /* check for bad input before we  */
222         return L2_ERR_ARG; /* dereference and throw a SIGSEV */
223 
224     while ((iCnt++ < L2_PIPE_MAXARGS) && (*szBuf != '\0')) {
225         while ((*szBuf == ' ') || (*szBuf == '\t'))
226             *szBuf++ = '\0'; /* overwrite whitespace with EOL  */
227         *szArgs++ = szBuf;   /* found the start of a new token */
228         while ((*szBuf != '\0') && (*szBuf != ' ') && (*szBuf != '\t'))
229             szBuf++;
230     }
231     *szArgs = '\0'; /* add a NULL to mark the end of the chain */
232 
233     if (iCnt <= L2_PIPE_MAXARGS)
234         return L2_OK;
235     else
236         return L2_ERR_ARG;
237 }
238 
239 /************************************************************
240  * spawn_command: Helper method to hook_open and hook_write *
241  *   Forks a new process, and copies the command executable *
242  ************************************************************/
spawn_command(l2_ch_pipe_t * cfg)243 static l2_result_t spawn_command(l2_ch_pipe_t *cfg)
244 {
245     char *pVec[L2_PIPE_MAXARGS];
246     char *sz = NULL;
247     l2_result_t rv;
248 
249     /* initialize auto vars before using them */
250     memset(pVec, 0, sizeof(pVec));
251 
252     /* spawn a child process to be later overwritten by the user command  */
253     if ((cfg->Pid = fork()) > 0) {            /* parent process           */
254         free(sz);                             /* no exec() in parent      */
255         close(cfg->piFd[0]);                  /* half-duplex (no reading) */
256         cfg->piFd[0] = -1;
257         return L2_OK;
258     }
259     else if (cfg->Pid == 0) {                 /* child process            */
260         close(cfg->piFd[1]);                  /* close the writing end,   */
261         cfg->piFd[1] = -1;                    /* because we don't use it  */
262         dup2(cfg->piFd[0], fileno(stdin));    /* copy the reading end     */
263 
264         /* redirection of child's stdout and stdin */
265         cfg->iNulldev = open("/dev/null", O_RDWR);
266         dup2(cfg->iNulldev, fileno(stdout));      /* redirect stdout to null  */
267         dup2(cfg->iNulldev, fileno(stderr));      /* redirect stderr to null  */
268 
269         /* the distinction between modes is necessary, because only executing */
270         /* commands in a shell environment allows usage of variables and such */
271         if (cfg->iMode == L2_PIPE_EXECMODE_SHELL) {
272             pVec[0] = "/bin/sh";
273             pVec[1] = "-c";
274             pVec[2] = cfg->szCmdpath;
275             pVec[3] = NULL;        /* add a NULL to mark the end of the chain */
276         }
277         else { /* plain direct command execution */
278             sz = strdup(cfg->szCmdpath);
279             if ((rv = parse_cmdpath(sz, pVec)) != L2_OK) {
280                 free(sz);
281                 return rv;
282             }
283         }
284 
285         if (execvp(*pVec, pVec) == -1) {      /* launch                   */
286             TRACE("execvp in child returned -1");
287             free(sz);                         /* cleanup in case we fail  */
288             close(cfg->piFd[0]);
289             cfg->piFd[0] = -1; /* if execvp() doesn't swap our context or */
290             return L2_ERR_SYS; /* if child returns, we have an error      */
291         }
292         else
293             return L2_OK; /* NOTREACHED */
294     }
295     else /* fork failed  */
296         return L2_ERR_SYS;
297 }
298 
299 /* open channel */
hook_open(l2_context_t * ctx,l2_channel_t * ch)300 static l2_result_t hook_open(l2_context_t *ctx, l2_channel_t *ch)
301 {
302     l2_ch_pipe_t *cfg = (l2_ch_pipe_t *)ctx->vp;
303     struct sigaction locact;
304 
305     /* consistency check */
306     if (cfg->szCmdpath == NULL)
307         return L2_ERR_USE;
308 
309     /* initialize auto vars before using them */
310     memset(&locact, 0, sizeof(locact));
311 
312     locact.sa_handler = (void(*)(int))catchsignal;
313     sigemptyset(&locact.sa_mask);
314     locact.sa_flags = 0;
315 
316     /* save old signal context before replacing with our own */
317     if (sigaction(SIGCHLD, &locact, &cfg->sigchld) < 0)
318         return L2_ERR_SYS;
319     if (sigaction(SIGPIPE, &locact, &cfg->sigpipe) < 0)
320         return L2_ERR_SYS;
321 
322     if (pipe(cfg->piFd) == -1) /* open the pipe */
323         return L2_ERR_SYS;
324 
325     /* short circuit hack, if in oneshot mode and not yet opened then return */
326     if ((cfg->iRtme == L2_PIPE_RUNTIME_ONESHOT) && (ch->state != L2_CHSTATE_OPENED))
327         return L2_OK;
328     else
329         return spawn_command(cfg); /* spawn the command process */
330 }
331 
332 /* write to channel, possibly recursively */
hook_write(l2_context_t * ctx,l2_channel_t * ch,l2_level_t level,const char * buf,size_t buf_size)333 static l2_result_t hook_write(l2_context_t *ctx, l2_channel_t *ch,
334                               l2_level_t level, const char *buf, size_t buf_size)
335 {
336     l2_result_t   rv      = L2_OK;
337     l2_ch_pipe_t *cfg     = (l2_ch_pipe_t *)ctx->vp;
338 
339     /* spawn the child command process if we are in oneshot mode */
340     if ((cfg->iRtme == L2_PIPE_RUNTIME_ONESHOT) && (cfg->Pid == -1))
341         if (spawn_command(cfg) != L2_OK)
342             return L2_ERR_SYS; /* immediate return if we can't spawn command */
343 
344     /* write message to channel pipe */
345     if (write(cfg->piFd[1], buf, buf_size) == -1) {
346         if ((errno == EPIPE) && (cfg->iWritefail++ < L2_PIPE_WRITEFAIL)) {
347             if ((rv = l2_channel_close(ch)) != L2_OK)
348                 return rv;
349             if ((rv = l2_channel_open(ch)) != L2_OK)
350                 return rv;
351             return hook_write(ctx, ch, level, buf, buf_size);
352         }
353         else { /* not broken pipe problem or over the fail limit, so panic */
354             cfg->iWritefail = 0;             /* reset pipe failure counter */
355             rv = L2_ERR_SYS;
356         }
357     }
358     else                     /* write() to pipe succeeded  */
359         cfg->iWritefail = 0; /* reset pipe failure counter */
360 
361     /* block until child terminates if in oneshot execmode */
362     if ((cfg->iRtme == L2_PIPE_RUNTIME_ONESHOT) && (cfg->Pid != -1))
363         cfg->Pid = waitpid(cfg->Pid, NULL, WUNTRACED | WNOHANG);
364 
365     return rv;
366 }
367 
368 /* close channel */
hook_close(l2_context_t * ctx,l2_channel_t * ch)369 static l2_result_t hook_close(l2_context_t *ctx, l2_channel_t *ch)
370 {
371     l2_result_t   rv      = L2_OK;
372     l2_ch_pipe_t *cfg     = (l2_ch_pipe_t *)ctx->vp;
373 
374     /* close null device */
375     if (cfg->iNulldev != -1) {
376         close(cfg->iNulldev);
377         cfg->iNulldev = -1;
378     }
379 
380     /* close output pipe for parent */
381     if (cfg->piFd[1] != -1) {
382         close(cfg->piFd[1]);
383         cfg->piFd[1] = -1;
384     }
385 
386     /* restore previous signal context, but only if it was saved and replaced */
387     if (&cfg->sigchld.sa_handler) {
388         if (sigaction(SIGCHLD, &cfg->sigchld, 0) < 0)
389             rv = L2_ERR_SYS;
390         if (sigaction(SIGPIPE, &cfg->sigpipe, 0) < 0)
391             rv = L2_ERR_SYS;
392     }
393 
394     /* kill child process if still running */
395     if (cfg->Pid != -1) {
396         kill(cfg->Pid, SIGTERM);
397         cfg->Pid = waitpid(cfg->Pid, NULL, WUNTRACED | WNOHANG);
398         cfg->Pid = -1;
399     }
400 
401     return rv;
402 }
403 
404 /* destroy channel */
hook_destroy(l2_context_t * ctx,l2_channel_t * ch)405 static l2_result_t hook_destroy(l2_context_t *ctx, l2_channel_t *ch)
406 {
407     l2_ch_pipe_t *cfg = (l2_ch_pipe_t *)ctx->vp;
408 
409     /* destroy channel configuration */
410     free(cfg->szCmdpath);
411     cfg->szCmdpath = NULL;
412     free(cfg);
413 
414     return L2_OK;
415 }
416 
417 /* exported channel handler structure */
418 l2_handler_t l2_handler_pipe = {
419     "pipe",
420     L2_CHANNEL_OUTPUT,
421     hook_create,
422     hook_configure,
423     hook_open,
424     hook_write,
425     NULL,
426     hook_close,
427     hook_destroy
428 };
429 
430