event_exec.c 6.61 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 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 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 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 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 197 198 199 200 201 202 203 204 205 206 207 208 209
/* Icecast
 *
 * This program is distributed under the GNU General Public License, version 2.
 * A copy of this license is included with this source.
 *
 * Copyright 2014,      Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>,
 */

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


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

#include "event.h"
#include "logging.h"
#define CATMODULE "event_exec"


typedef struct event_exec {
/* REVIEW: Some ideas for future work:
   <option type="environ" name="TEST" value="Blubb" />
   <option name="argument" value="--test" />
   <option type="argument" value="--test" />
   <option name="default_arguments" value="false" />
*/
    /* name and path of executable */
    char *executable;
} event_exec_t;

#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
static inline void __setup_empty_script_environment(event_exec_t *self, event_t *event) {
    ice_config_t * config = config_get_config();
    mount_proxy *mountinfo;
    source_t *source;
    char buf[80];
    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);
    }

    __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 */
    __update_environ("SOURCE_ACTION",     event->trigger); /* old name */
    __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);

    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);
                    /* consider to add action here as well */
                    if (event->uri) {
                        execl(self->executable, self->executable, event->uri, (char *)NULL);
                    } else {
                        execl(self->executable, self->executable, (char *)NULL);
                    }
                    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
    ICECAST_LOG_ERROR("<event type=\"exec\" ...> not supported on win*");
#else
    _run_script(self, event);
#endif
    return 0;
}

static void event_exec_free(void *state) {
    event_exec_t *self = state;
    free(self);
}

int event_get_exec(event_registration_t *er, config_options_t *options) {
    event_exec_t *self = calloc(1, sizeof(event_exec_t));

    if (!self)
        return -1;

    if (options) {
        do {
            if (options->type)
                continue;
            if (!options->name)
                continue;
            /* BEFORE RELEASE 2.4.2 DOCUMENT: Document supported options:
             * <option name="executable" value="..." />
             */
            if (strcmp(options->name, "executable") == 0) {
                free(self->executable);
                self->executable = NULL;
                if (options->value)
                    self->executable = strdup(options->value);
            } else {
                ICECAST_LOG_ERROR("Unknown <option> tag with name %s.", options->name);
            }
        } while ((options = options->next));
    }

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

    er->state = self;
    er->emit = event_exec_emit;
    er->free = event_exec_free;
    return 0;
}