event_exec.c 10.7 KB
Newer Older
1
2
3
4
5
/* Icecast
 *
 * This program is distributed under the GNU General Public License, version 2.
 * A copy of this license is included with this source.
 *
6
 * Copyright 2014-2018, Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>,
7
8
9
10
11
12
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

Philipp Schafft's avatar
Philipp Schafft committed
13
14
#include <string.h>
#include <errno.h>
15
16
17
18
19
20
21
22
23

#ifndef _WIN32
#include <sys/wait.h>
/* for __setup_empty_script_environment() */
#include <sys/stat.h>
#include <fcntl.h>
#endif

#include "event.h"
24
#include "global.h"
25
#include "source.h"
26
27
28
#include "logging.h"
#define CATMODULE "event_exec"

29
30
31
32
33
34
35
36
typedef enum event_exec_argvtype_tag {
    ARGVTYPE_NO_DEFAULTS = 0,
    ARGVTYPE_ONLY_URI,
    ARGVTYPE_URI_AND_TRIGGER,
    ARGVTYPE_LEGACY,

    ARGVTYPE_DFAULT = ARGVTYPE_LEGACY
} event_exec_argvtype_t;
37
38
39
40

typedef struct event_exec {
    /* name and path of executable */
    char *executable;
41
42
43
44
45
46

    /* what to add to argv[] */
    event_exec_argvtype_t argvtype;

    /* actual argv[] */
    char **argv;
47
48
} event_exec_t;

49
50
51
52
53
54
55
56
57
58
59
/* OS independed code: */
static inline size_t __argvtype2offset(event_exec_argvtype_t argvtype) {
    switch (argvtype) {
        case ARGVTYPE_NO_DEFAULTS:     return 1; break;
        case ARGVTYPE_ONLY_URI:        return 2; break;
        case ARGVTYPE_URI_AND_TRIGGER: return 3; break;
        case ARGVTYPE_LEGACY:          return 2; break;
        default: return 0; break; /* This should never happen. */
    }
}

60
/* BEFORE RELEASE 2.5.0 DOCUMENT: Document names of possible values. */
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
static inline event_exec_argvtype_t __str2argvtype(const char *str) {
    if (!str)
        str = "(BAD VALUE)";

    if (strcmp(str, "default") == 0) {
        return ARGVTYPE_DFAULT;
    } else if (strcmp(str, "no_defaults") == 0) {
        return ARGVTYPE_NO_DEFAULTS;
    } else if (strcmp(str, "uri") == 0) {
        return ARGVTYPE_ONLY_URI;
    } else if (strcmp(str, "uri_and_trigger") == 0) {
        return ARGVTYPE_URI_AND_TRIGGER;
    } else if (strcmp(str, "legacy") == 0) {
        return ARGVTYPE_LEGACY;
    } else {
76
        ICECAST_LOG_ERROR("Unknown argument type %s, using \"default\"", str);
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
        return ARGVTYPE_DFAULT;
    }
}

static inline char **__setup_argv(event_exec_t *self, event_t *event) {
    self->argv[0] = self->executable;

    switch (self->argvtype) {
        case ARGVTYPE_NO_DEFAULTS:
            /* nothing to do */
        break;
        case ARGVTYPE_URI_AND_TRIGGER:
            self->argv[2] = event->trigger ? event->trigger : "";
        /* fall through */
        case ARGVTYPE_ONLY_URI:
            self->argv[1] = event->uri ? event->uri : "";
        break;
        case ARGVTYPE_LEGACY:
            /* This mode is similar to ARGVTYPE_ONLY_URI
             * but if URI is unknown the parameter is skipped!
             */
            if (event->uri) {
                self->argv[1] = event->uri;
            } else {
                self->argv[1] = self->executable;
                return &self->argv[1];
            }
        break;
    }

    return self->argv;
}

/* OS depended code: */
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#ifdef _WIN32
/* TODO #2101: Implement script executing on win* */
#else
/* this sets up the new environment for script execution.
 * We ignore most failtures as we can not handle them anyway.
 */
#ifdef HAVE_SETENV
static inline void __update_environ(const char *name, const char *value) {
    if (!name || !value) return;
    setenv(name, value, 1);
}
#else
#define __update_environ(x,y)
#endif
125
static inline void __setup_environ(ice_config_t *config, event_exec_t *self, event_t *event) {
126
127
128
129
    mount_proxy *mountinfo;
    source_t *source;
    char buf[80];

130
    /* BEFORE RELEASE 2.5.0 DOCUMENT: Document all those env vars. */
131
132
133
134
135
136
    __update_environ("ICECAST_VERSION",   ICECAST_VERSION_STRING);
    __update_environ("ICECAST_HOSTNAME",  config->hostname);
    __update_environ("ICECAST_ADMIN",     config->admin);
    __update_environ("ICECAST_LOGDIR",    config->log_dir);
    __update_environ("EVENT_URI",         event->uri);
    __update_environ("EVENT_TRIGGER",     event->trigger); /* new name */
137
    __update_environ("SOURCE_ACTION",     event->trigger); /* old name (deprecated) */
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
    __update_environ("CLIENT_IP",         event->connection_ip);
    __update_environ("CLIENT_ROLE",       event->client_role);
    __update_environ("CLIENT_USERNAME",   event->client_username);
    __update_environ("CLIENT_USERAGENT",  event->client_useragent);

    snprintf(buf, sizeof(buf), "%lu", event->connection_id);
    __update_environ("CLIENT_ID",         buf);
    snprintf(buf, sizeof(buf), "%lli", (long long int)event->connection_time);
    __update_environ("CLIENT_CONNECTION_TIME", buf);
    snprintf(buf, sizeof(buf), "%i", event->client_admin_command);
    __update_environ("CLIENT_ADMIN_COMMAND", buf);

    mountinfo = config_find_mount(config, event->uri, MOUNT_TYPE_NORMAL);
    if (mountinfo) {
        __update_environ("MOUNT_NAME",        mountinfo->stream_name);
        __update_environ("MOUNT_DESCRIPTION", mountinfo->stream_description);
        __update_environ("MOUNT_URL",         mountinfo->stream_url);
        __update_environ("MOUNT_GENRE",       mountinfo->stream_genre);
    }

    avl_tree_rlock(global.source_tree);
    source = source_find_mount(event->uri);
    if (source) {
        __update_environ("SOURCE_MOUNTPOINT", source->mount);
        __update_environ("SOURCE_PUBLIC",     source->yp_public ? "true" : "false");
        __update_environ("SROUCE_HIDDEN",     source->hidden    ? "true" : "false");
    }
    avl_tree_unlock(global.source_tree);
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
}

static inline void __setup_file_descriptors(ice_config_t *config) {
    int i;

    /* close at least the first 1024 handles */
    for (i = 0; i < 1024; i++)
        close(i);

    /* open null device */
    i = open(config->null_device, O_RDWR);
    if (i != -1) {
        /* attach null device to stdin, stdout and stderr */
        if (i != 0)
            dup2(i, 0);
        if (i != 1)
            dup2(i, 1);
        if (i != 2)
            dup2(i, 2);

        /* close null device */
        if (i > 2)
            close(i);
    }
}

static inline void __setup_empty_script_environment(event_exec_t *self, event_t *event) {
    ice_config_t *config = config_get_config();

    __setup_file_descriptors(config);
    __setup_environ(config, self, event);
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220

    config_release_config();
}

static void _run_script (event_exec_t *self, event_t *event) {
    pid_t pid, external_pid;

    /* do a fork twice so that the command has init as parent */
    external_pid = fork();
    switch (external_pid)
    {
        case 0:
            switch (pid = fork ())
            {
                case -1:
                    ICECAST_LOG_ERROR("Unable to fork %s (%s)", self->executable, strerror (errno));
                    break;
                case 0:  /* child */
                    if (access(self->executable, R_OK|X_OK) != 0) {
                        ICECAST_LOG_ERROR("Unable to run command %s (%s)", self->executable, strerror(errno));
                        exit(1);
                    }
                    ICECAST_LOG_DEBUG("Starting command %s", self->executable);
                    __setup_empty_script_environment(self, event);
221
                    execv(self->executable, __setup_argv(self, event));
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
                    exit(1);
                default: /* parent */
                    break;
            }
            exit (0);
        case -1:
            ICECAST_LOG_ERROR("Unable to fork %s", strerror (errno));
            break;
        default: /* parent */
            waitpid (external_pid, NULL, 0);
            break;
    }
}
#endif

static int event_exec_emit(void *state, event_t *event) {
    event_exec_t *self = state;
#ifdef _WIN32
240
241
    /* BEFORE RELEASE 2.5.0 DOCUMENT: Document this not working on win*. */
    ICECAST_LOG_ERROR("<event type=\"exec\" ...> not supported on Windows");
242
243
244
245
246
247
248
249
#else
    _run_script(self, event);
#endif
    return 0;
}

static void event_exec_free(void *state) {
    event_exec_t *self = state;
250
251
252
253
254
255
256
    size_t i;

    for (i = __argvtype2offset(self->argvtype); self->argv[i]; i++)
        free(self->argv[i]);

    free(self->argv);
    free(self->executable);
257
258
259
260
261
    free(self);
}

int event_get_exec(event_registration_t *er, config_options_t *options) {
    event_exec_t *self = calloc(1, sizeof(event_exec_t));
262
263
    config_options_t *cur;
    size_t extra_argc = 0;
264
265
266
267

    if (!self)
        return -1;

268
269
270
    self->argvtype = ARGVTYPE_DFAULT;

    if ((cur = options)) {
271
        do {
272
            if (cur->name) {
273
                /* BEFORE RELEASE 2.5.0 DOCUMENT: Document supported options:
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
                 * <option name="executable" value="..." />
                 * <option name="default_arguments" value="..." /> (for values see near top of documment)
                 */
                if (strcmp(cur->name, "executable") == 0) {
                    free(self->executable);
                    self->executable = NULL;
                    if (cur->value)
                        self->executable = strdup(cur->value);
                } else if (strcmp(cur->name, "default_arguments") == 0) {
                    self->argvtype = __str2argvtype(cur->value);
                } else {
                    ICECAST_LOG_ERROR("Unknown <option> tag with name %s.", cur->name);
                }
            } else if (cur->type) {
                if (strcmp(cur->type, "argument") == 0) {
                    extra_argc++;
                } else {
                    ICECAST_LOG_ERROR("Unknown <option> tag with type %s.", cur->type);
                }
293
            }
294
        } while ((cur = cur->next));
295
296
297
298
299
300
301
302
    }

    if (!self->executable) {
        ICECAST_LOG_ERROR("No executable given.");
        event_exec_free(self);
        return -1;
    }

303
304
305
306
307
308
309
310
311
312
313
314
    self->argv = calloc(__argvtype2offset(self->argvtype) + extra_argc + 1, sizeof(char*));
    if (!self->argv) {
        ICECAST_LOG_ERROR("Can not allocate argv[]");
        event_exec_free(self);
        return -1;
    }

    extra_argc = __argvtype2offset(self->argvtype);

    if ((cur = options)) {
        do {
            if (cur->type) {
315
                /* BEFORE RELEASE 2.5.0 DOCUMENT: Document supported options:
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
                 * <option type="argument" value="..." />
                 */
                if (strcmp(cur->type, "argument") == 0) {
                    if (cur->value) {
                        self->argv[extra_argc] = strdup(cur->value);
                        if (!self->argv[extra_argc]) {
                            ICECAST_LOG_ERROR("Can not allocate argv[x]");
                            event_exec_free(self);
                            return -1;
                        }
                        extra_argc++;
                    }
                } else {
                    ICECAST_LOG_ERROR("Unknown <option> tag with type %s.", cur->type);
                }
            }
        } while ((cur = cur->next));
    }

335
336
337
338
339
    er->state = self;
    er->emit = event_exec_emit;
    er->free = event_exec_free;
    return 0;
}