#include "uwsgi.h" extern struct uwsgi_server uwsgi; /* advanced (pluggable) hooks they are executed before the other hooks, and can be extended by plugins if a plugin tries to register an hook with a name already available in the list, its function will be overridden */ struct uwsgi_hook *uwsgi_hook_by_name(char *name) { struct uwsgi_hook *uh = uwsgi.hooks; while(uh) { if (!strcmp(uh->name, name)) { return uh; } uh = uh->next; } return NULL; } void uwsgi_register_hook(char *name, int (*func)(char *)) { struct uwsgi_hook *old_uh = NULL, *uh = uwsgi.hooks; while(uh) { if (!strcmp(uh->name, name)) { uh->func = func; return; } old_uh = uh; uh = uh->next; } uh = uwsgi_calloc(sizeof(struct uwsgi_hook)); uh->name = name; uh->func = func; if (old_uh) { old_uh->next = uh; } else { uwsgi.hooks = uh; } } static int uwsgi_hook_alarm(char *arg) { char *space = strchr(arg,' '); if (!space) { uwsgi_log("invalid alarm hook syntax, must be: \n"); return -1; } *space = 0; uwsgi_alarm_trigger(arg, space+1, strlen(space+1)); *space = ' '; return 0; } static int uwsgi_hook_chdir(char *arg) { int ret = chdir(arg); if (ret) { uwsgi_error("uwsgi_hook_chdir()"); } return ret; } static int uwsgi_hook_mkdir(char *arg) { int ret = mkdir(arg, 0777); if (ret) { uwsgi_error("uwsgi_hook_mkdir()"); } return ret; } static int uwsgi_hook_putenv(char *arg) { int ret = putenv(arg); if (ret) { uwsgi_error("uwsgi_hook_putenv()"); } return ret; } static int uwsgi_hook_exec(char *arg) { int ret = uwsgi_run_command_and_wait(NULL, arg); if (ret != 0) { uwsgi_log("command \"%s\" exited with non-zero code: %d\n", arg, ret); } return ret; } static int uwsgi_hook_safeexec(char *arg) { int ret = uwsgi_run_command_and_wait(NULL, arg); if (ret != 0) { uwsgi_log("command \"%s\" exited with non-zero code: %d\n", arg, ret); } return 0; } static int uwsgi_hook_exit(char *arg) { int exit_code = 0; if (strlen(arg) > 1) { exit_code = atoi(arg); } exit(exit_code); } static int uwsgi_hook_print(char *arg) { char *line = uwsgi_concat2(arg, "\n"); uwsgi_log(line); free(line); return 0; } static int uwsgi_hook_unlink(char *arg) { int ret = unlink(arg); if (ret) { uwsgi_error("uwsgi_hook_unlink()/unlink()"); } return ret; } static int uwsgi_hook_writefifo(char *arg) { char *space = strchr(arg, ' '); if (!space) { uwsgi_log("invalid hook writefifo syntax, must be: \n"); return -1; } *space = 0; int fd = open(arg, O_WRONLY|O_NONBLOCK); if (fd < 0) { uwsgi_error_open(arg); *space = ' '; if (errno == ENODEV) return 0; #ifdef ENXIO if (errno == ENXIO) return 0; #endif return -1; } *space = ' '; size_t l = strlen(space+1); if (write(fd, space+1, l) != (ssize_t) l) { uwsgi_error("uwsgi_hook_writefifo()/write()"); close(fd); return -1; } close(fd); return 0; } static int uwsgi_hook_write(char *arg) { char *space = strchr(arg, ' '); if (!space) { uwsgi_log("invalid hook write syntax, must be: \n"); return -1; } *space = 0; int fd = open(arg, O_WRONLY|O_CREAT|O_TRUNC, 0666); if (fd < 0) { uwsgi_error_open(arg); *space = ' '; return -1; } *space = ' '; size_t l = strlen(space+1); if (write(fd, space+1, l) != (ssize_t) l) { uwsgi_error("uwsgi_hook_write()/write()"); close(fd); return -1; } close(fd); return 0; } static int uwsgi_hook_creat(char *arg) { int fd = open(arg, O_WRONLY|O_CREAT|O_TRUNC, 0666); if (fd < 0) { uwsgi_error_open(arg); return -1; } close(fd); return 0; } static int uwsgi_hook_append(char *arg) { char *space = strchr(arg, ' '); if (!space) { uwsgi_log("invalid hook append syntax, must be: \n"); return -1; } *space = 0; int fd = open(arg, O_WRONLY|O_CREAT|O_APPEND, 0666); if (fd < 0) { uwsgi_error_open(arg); *space = ' '; return -1; } *space = ' '; size_t l = strlen(space+1); if (write(fd, space+1, l) != (ssize_t) l) { uwsgi_error("uwsgi_hook_append()/write()"); close(fd); return -1; } close(fd); return 0; } static int uwsgi_hook_writen(char *arg) { char *space = strchr(arg, ' '); if (!space) { uwsgi_log("invalid hook writen syntax, must be: \n"); return -1; } *space = 0; int fd = open(arg, O_WRONLY|O_CREAT|O_TRUNC, 0666); if (fd < 0) { uwsgi_error_open(arg); *space = ' '; return -1; } *space = ' '; size_t l = strlen(space+1); char *buf = uwsgi_malloc(l + 1); memcpy(buf, space+1, l); buf[l] = '\n'; if (write(fd, buf, l+1) != (ssize_t) (l+1)) { uwsgi_error("uwsgi_hook_writen()/write()"); free(buf); close(fd); return -1; } free(buf); close(fd); return 0; } static int uwsgi_hook_appendn(char *arg) { char *space = strchr(arg, ' '); if (space) *space = 0; int fd = open(arg, O_WRONLY|O_CREAT|O_APPEND, 0666); if (fd < 0) { uwsgi_error_open(arg); if (space) *space = ' '; return -1; } if (!space) { // simple newline if (write(fd, "\n", 1) != 1) { uwsgi_error("uwsgi_hook_appendn()/write()"); close(fd); return -1; } close(fd); return 0; } *space = ' '; size_t l = strlen(space+1); char *buf = uwsgi_malloc(l + 1); memcpy(buf, space+1, l); buf[l] = '\n'; if (write(fd, buf, l+1) != (ssize_t) (l+1)) { uwsgi_error("uwsgi_hook_appendn()/write()"); free(buf); close(fd); return -1; } free(buf); close(fd); return 0; } static int uwsgi_hook_chmod(char *arg) { char *space = strchr(arg, ' '); if (!space) { uwsgi_log("invalid hook chmod syntax, must be: \n"); return -1; } *space = 0; int error = 0; mode_t mask = uwsgi_mode_t(space+1, &error); if (error) { uwsgi_log("invalid hook chmod mask: %s\n", space+1); *space = ' '; return -1; } int ret = chmod(arg, mask); *space = ' '; if (ret) { uwsgi_error("uwsgi_hook_chmod()/chmod()"); } return ret; } static int uwsgi_hook_sticky(char *arg) { struct stat st; if (stat(arg, &st)) { uwsgi_error("uwsgi_hook_sticky()/stat()"); return -1; } if (chmod(arg, st.st_mode | S_ISVTX)) { uwsgi_error("uwsgi_hook_sticky()/chmod()"); return -1; } return 0; } static int uwsgi_hook_chown(char *arg) { char *space = strchr(arg, ' '); if (!space) { uwsgi_log("invalid hook chown syntax, must be: \n"); return -1; } *space = 0; char *space2 = strchr(space+1, ' '); if (!space2) { *space = ' '; uwsgi_log("invalid hook chown syntax, must be: \n"); return -1; } *space2 = 0; struct passwd *pw = getpwnam(space+1); if (!pw) { uwsgi_log("unable to find uid %s\n", space+1); *space = ' '; *space2 = ' '; return -1; } struct group *gr = getgrnam(space2+1); if (!gr) { uwsgi_log("unable to find gid %s\n", space2+1); *space = ' '; *space2 = ' '; return -1; } int ret = chown(arg, pw->pw_uid, gr->gr_gid); *space = ' '; *space2 = ' '; if (ret) { uwsgi_error("uwsgi_hook_chown()/chown)"); } return ret; } static int uwsgi_hook_chown2(char *arg) { char *space = strchr(arg, ' '); if (!space) { uwsgi_log("invalid hook chown2 syntax, must be: \n"); return -1; } *space = 0; char *space2 = strchr(space+1, ' '); if (!space2) { *space = ' '; uwsgi_log("invalid hook chown2 syntax, must be: \n"); return -1; } *space2 = 0; if (!is_a_number(space+1)) { uwsgi_log("invalid hook chown2 syntax, uid must be a number\n"); *space = ' '; *space2 = ' '; return -1; } if (!is_a_number(space2+1)) { uwsgi_log("invalid hook chown2 syntax, gid must be a number\n"); *space = ' '; *space2 = ' '; return -1; } int ret = chown(arg, atoi(space+1), atoi(space2+1)); *space = ' '; *space2 = ' '; if (ret) { uwsgi_error("uwsgi_hook_chown2()/chown)"); } return ret; } #if defined(UWSGI_SUNOS_EXTERN_SETHOSTNAME) extern int sethostname(char *, int); #endif static int uwsgi_hook_hostname(char *arg) { #ifdef __CYGWIN__ return -1; #else return sethostname(arg, strlen(arg)); #endif } static int uwsgi_hook_unix_signal(char *arg) { char *space = strchr(arg, ' '); if (!space) { uwsgi_log("invalid unix_signal syntax, must be \n"); return -1; } *space = 0; int signum = atoi(arg); *space = ' '; void (*func)(int) = dlsym(RTLD_DEFAULT, space+1); if (!func) { uwsgi_log("unable to find function \"%s\"\n", space+1); return -1; } uwsgi_unix_signal(signum, func); return 0; } static int uwsgi_hook_callint(char *arg) { char *space = strchr(arg, ' '); if (space) { *space = 0; int num = atoi(space+1); void (*func)(int) = dlsym(RTLD_DEFAULT, arg); if (!func) { uwsgi_log("unable to call function \"%s(%d)\"\n", arg, num); *space = ' '; return -1; } *space = ' '; func(num); } else { void (*func)(void) = dlsym(RTLD_DEFAULT, arg); if (!func) { uwsgi_log("unable to call function \"%s\"\n", arg); return -1; } func(); } return 0; } static int uwsgi_hook_call(char *arg) { char *space = strchr(arg, ' '); if (space) { *space = 0; void (*func)(char *) = dlsym(RTLD_DEFAULT, arg); if (!func) { uwsgi_log("unable to call function \"%s(%s)\"\n", arg, space + 1); *space = ' '; return -1; } *space = ' '; func(space + 1); } else { void (*func)(void) = dlsym(RTLD_DEFAULT, arg); if (!func) { uwsgi_log("unable to call function \"%s\"\n", arg); return -1; } func(); } return 0; } static int uwsgi_hook_callintret(char *arg) { char *space = strchr(arg, ' '); if (space) { *space = 0; int num = atoi(space+1); int (*func)(int) = dlsym(RTLD_DEFAULT, arg); if (!func) { uwsgi_log("unable to call function \"%s(%d)\"\n", arg, num); *space = ' '; return -1; } *space = ' '; return func(num); } int (*func)(void) = dlsym(RTLD_DEFAULT, arg); if (!func) { uwsgi_log("unable to call function \"%s\"\n", arg); return -1; } return func(); } static int uwsgi_hook_callret(char *arg) { char *space = strchr(arg, ' '); if (space) { *space = 0; int (*func)(char *) = dlsym(RTLD_DEFAULT, arg); if (!func) { uwsgi_log("unable to call function \"%s(%s)\"\n", arg, space + 1); *space = ' '; return -1; } *space = ' '; return func(space + 1); } int (*func)(void) = dlsym(RTLD_DEFAULT, arg); if (!func) { uwsgi_log("unable to call function \"%s\"\n", arg); return -1; } return func(); } static int uwsgi_hook_rpc(char *arg) { int ret = -1; size_t i, argc = 0; char **rargv = uwsgi_split_quoted(arg, strlen(arg), " \t", &argc); if (!argc) goto end; if (argc > 256) goto destroy; char *argv[256]; uint16_t argvs[256]; char *node = NULL; char *func = rargv[0]; char *at = strchr(func, '@'); if (at) { *at = 0; node = at + 1; } for(i=0;i<(argc-1);i++) { size_t a_len = strlen(rargv[i+1]); if (a_len > 0xffff) goto destroy; argv[i] = rargv[i+1] ; argvs[i] = a_len; } uint64_t size = 0; // response must be always freed char *response = uwsgi_do_rpc(node, func, argc-1, argv, argvs, &size); if (response) { if (at) *at = '@'; uwsgi_log("[rpc result from \"%s\"] %.*s\n", rargv[0], size, response); free(response); ret = 0; } destroy: for(i=0;i \n"); return -1; } *space = 0; retry: uwsgi_log("waiting for %s ...\n", arg); fd = open(arg, O_WRONLY|O_NONBLOCK); if (fd < 0) { if (errno == ENODEV || errno == ENOENT) { sleep(1); goto retry; } #ifdef ENXIO if (errno == ENXIO) { sleep(1); goto retry; } #endif uwsgi_error_open(arg); *space = ' '; return -1; } *space = ' '; size_t l = strlen(space+1); if (write(fd, space+1, l) != (ssize_t) l) { uwsgi_error("spinningfifo_hook()/write()"); close(fd); return -1; } close(fd); return 0; } void uwsgi_register_base_hooks() { uwsgi_register_hook("cd", uwsgi_hook_chdir); uwsgi_register_hook("chdir", uwsgi_hook_chdir); uwsgi_register_hook("mkdir", uwsgi_hook_mkdir); uwsgi_register_hook("putenv", uwsgi_hook_putenv); uwsgi_register_hook("chmod", uwsgi_hook_chmod); uwsgi_register_hook("chown", uwsgi_hook_chown); uwsgi_register_hook("chown2", uwsgi_hook_chown2); uwsgi_register_hook("sticky", uwsgi_hook_sticky); uwsgi_register_hook("exec", uwsgi_hook_exec); uwsgi_register_hook("safeexec", uwsgi_hook_safeexec); uwsgi_register_hook("create", uwsgi_hook_creat); uwsgi_register_hook("creat", uwsgi_hook_creat); uwsgi_register_hook("write", uwsgi_hook_write); uwsgi_register_hook("writen", uwsgi_hook_writen); uwsgi_register_hook("append", uwsgi_hook_append); uwsgi_register_hook("appendn", uwsgi_hook_appendn); uwsgi_register_hook("writefifo", uwsgi_hook_writefifo); uwsgi_register_hook("unlink", uwsgi_hook_unlink); uwsgi_register_hook("mount", uwsgi_mount_hook); uwsgi_register_hook("umount", uwsgi_umount_hook); uwsgi_register_hook("call", uwsgi_hook_call); uwsgi_register_hook("callret", uwsgi_hook_callret); uwsgi_register_hook("callint", uwsgi_hook_callint); uwsgi_register_hook("callintret", uwsgi_hook_callintret); uwsgi_register_hook("hostname", uwsgi_hook_hostname); uwsgi_register_hook("alarm", uwsgi_hook_alarm); uwsgi_register_hook("rpc", uwsgi_hook_rpc); uwsgi_register_hook("retryrpc", uwsgi_hook_retryrpc); uwsgi_register_hook("wait_for_fs", uwsgi_hook_wait_for_fs); uwsgi_register_hook("wait_for_file", uwsgi_hook_wait_for_file); uwsgi_register_hook("wait_for_dir", uwsgi_hook_wait_for_dir); uwsgi_register_hook("wait_for_socket", uwsgi_hook_wait_for_socket); uwsgi_register_hook("unix_signal", uwsgi_hook_unix_signal); uwsgi_register_hook("spinningfifo", spinningfifo_hook); // for testing uwsgi_register_hook("exit", uwsgi_hook_exit); uwsgi_register_hook("print", uwsgi_hook_print); uwsgi_register_hook("log", uwsgi_hook_print); } int uwsgi_hooks_run_and_return(struct uwsgi_string_list *l, char *phase, char *context, int fatal) { int final_ret = 0; struct uwsgi_string_list *usl = NULL; if (context) { if (setenv("UWSGI_HOOK_CONTEXT", context, 1)) { uwsgi_error("uwsgi_hooks_run_and_return()/setenv()"); return -1; } } uwsgi_foreach(usl, l) { char *colon = strchr(usl->value, ':'); if (!colon) { uwsgi_log("invalid hook syntax, must be hook:args\n"); exit(1); } *colon = 0; int private = 0; char *action = usl->value; // private hook ? if (action[0] == '!') { action++; private = 1; } struct uwsgi_hook *uh = uwsgi_hook_by_name(action); if (!uh) { uwsgi_log("hook action not found: %s\n", action); exit(1); } *colon = ':'; if (private) { uwsgi_log("running --- PRIVATE HOOK --- (%s)...\n", phase); } else { uwsgi_log("running \"%s\" (%s)...\n", usl->value, phase); } int ret = uh->func(colon+1); if (ret != 0) { if (fatal) { if (context) { unsetenv("UWSGI_HOOK_CONTEXT"); } return ret; } final_ret = ret; } } if (context) { unsetenv("UWSGI_HOOK_CONTEXT"); } return final_ret; } void uwsgi_hooks_run(struct uwsgi_string_list *l, char *phase, int fatal) { int ret = uwsgi_hooks_run_and_return(l, phase, NULL, fatal); if (fatal && ret != 0) { uwsgi_log_verbose("FATAL hook failed, destroying instance\n"); if (uwsgi.master_process) { if (uwsgi.workers) { if (uwsgi.workers[0].pid == getpid()) { kill_them_all(0); return; } else { if (kill(uwsgi.workers[0].pid, SIGINT)) { uwsgi_error("uwsgi_hooks_run()/kill()"); exit(1); } return; } } } exit(1); } } #if defined(__linux__) && !defined(OBSOLETE_LINUX_KERNEL) /* this is a special hook, allowing the Emperor to enter a vassal namespace and call hooks in its namespace context. */ void uwsgi_hooks_setns_run(struct uwsgi_string_list *l, pid_t pid, uid_t uid, gid_t gid) { int (*u_setns) (int, int) = (int (*)(int, int)) dlsym(RTLD_DEFAULT, "setns"); if (!u_setns) { uwsgi_log("your system misses setns() syscall !!!\n"); return; } struct uwsgi_string_list *usl = NULL; uwsgi_foreach(usl, l) { // fist of all fork() the current process pid_t new_pid = fork(); if (new_pid > 0) { // wait for its death int status; if (waitpid(new_pid, &status, 0) < 0) { uwsgi_error("uwsgi_hooks_setns_run()/waitpid()"); } } else if (new_pid == 0) { // from now on, freeing memory is useless // now split args to know which namespaces to join char *action = strchr(usl->value, ' '); if (!action) { uwsgi_log("invalid setns hook syntax, must be \"namespaces_list action:...\"\n"); exit(1); } char *pidstr = uwsgi_num2str(pid); char *uidstr = uwsgi_num2str(uid); char *gidstr = uwsgi_num2str(gid); char *namespaces = uwsgi_concat2n(usl->value, action-usl->value, "", 0); char *p, *ctx = NULL; uwsgi_foreach_token(namespaces, ",", p, ctx) { char *procfile = uwsgi_concat4("/proc/", pidstr, "/ns/", p); int fd = open(procfile, O_RDONLY); if (fd < 0) { uwsgi_error_open(procfile); exit(1); } if (u_setns(fd, 0) < 0){ uwsgi_error("uwsgi_hooks_setns_run()/setns()"); exit(1); } close(fd); free(procfile); } if (setenv("UWSGI_VASSAL_PID", pidstr, 1)) { uwsgi_error("uwsgi_hooks_setns_run()/setenv()"); exit(1); } if (setenv("UWSGI_VASSAL_UID", uidstr, 1)) { uwsgi_error("uwsgi_hooks_setns_run()/setenv()"); exit(1); } if (setenv("UWSGI_VASSAL_GID", gidstr, 1)) { uwsgi_error("uwsgi_hooks_setns_run()/setenv()"); exit(1); } // now run the action and then exit action++; char *colon = strchr(action, ':'); if (!colon) { uwsgi_log("invalid hook syntax must be action:arg\n"); exit(1); } *colon = 0; struct uwsgi_hook *uh = uwsgi_hook_by_name(action); if (!uh) { uwsgi_log("hook action not found: %s\n", action); exit(1); } *colon = ':'; uwsgi_log("running \"%s\" (setns)...\n", usl->value); exit(uh->func(colon+1)); } else { uwsgi_error("uwsgi_hooks_setns_run()/fork()"); } } } #endif