static int cgi_create_env(server *srv, connection *con, plugin_data *p, handler_ctx *hctx, buffer *cgi_handler) { pid_t pid; int to_cgi_fds[2]; int from_cgi_fds[2]; struct stat st; UNUSED(p); #ifndef __WIN32 if (!buffer_string_is_empty(cgi_handler)) { /* stat the exec file */ if (-1 == (stat(cgi_handler->ptr, &st))) { log_error_write(srv, __FILE__, __LINE__, "sbss", "stat for cgi-handler", cgi_handler, "failed:", strerror(errno)); return -1; } } if (pipe_cloexec(to_cgi_fds)) { log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed:", strerror(errno)); return -1; } if (pipe_cloexec(from_cgi_fds)) { close(to_cgi_fds[0]); close(to_cgi_fds[1]); log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed:", strerror(errno)); return -1; } /* fork, execve */ switch (pid = fork()) { case 0: { /* child */ char **args; int argc; int i = 0; char_array env; char *c; const char *s; http_cgi_opts opts = { 0, 0, NULL, NULL }; /* move stdout to from_cgi_fd[1] */ dup2(from_cgi_fds[1], STDOUT_FILENO); #ifndef FD_CLOEXEC close(from_cgi_fds[1]); /* not needed */ close(from_cgi_fds[0]); #endif /* move the stdin to to_cgi_fd[0] */ dup2(to_cgi_fds[0], STDIN_FILENO); #ifndef FD_CLOEXEC close(to_cgi_fds[0]); /* not needed */ close(to_cgi_fds[1]); #endif /* create environment */ env.ptr = NULL; env.size = 0; env.used = 0; http_cgi_headers(srv, con, &opts, cgi_env_add, &env); /* for valgrind */ if (NULL != (s = getenv("LD_PRELOAD"))) { cgi_env_add(&env, CONST_STR_LEN("LD_PRELOAD"), s, strlen(s)); } if (NULL != (s = getenv("LD_LIBRARY_PATH"))) { cgi_env_add(&env, CONST_STR_LEN("LD_LIBRARY_PATH"), s, strlen(s)); } #ifdef __CYGWIN__ /* CYGWIN needs SYSTEMROOT */ if (NULL != (s = getenv("SYSTEMROOT"))) { cgi_env_add(&env, CONST_STR_LEN("SYSTEMROOT"), s, strlen(s)); } #endif if (env.size == env.used) { env.size += 16; env.ptr = realloc(env.ptr, env.size * sizeof(*env.ptr)); } env.ptr[env.used] = NULL; /* set up args */ argc = 3; args = malloc(sizeof(*args) * argc); force_assert(args); i = 0; if (!buffer_string_is_empty(cgi_handler)) { args[i++] = cgi_handler->ptr; } args[i++] = con->physical.path->ptr; args[i ] = NULL; /* search for the last / */ if (NULL != (c = strrchr(con->physical.path->ptr, '/'))) { /* handle special case of file in root directory */ const char* physdir = (c == con->physical.path->ptr) ? "/" : con->physical.path->ptr; /* temporarily shorten con->physical.path to directory without terminating '/' */ *c = '\0'; /* change to the physical directory */ if (-1 == chdir(physdir)) { log_error_write(srv, __FILE__, __LINE__, "ssb", "chdir failed:", strerror(errno), con->physical.path); } *c = '/'; } /* we don't need the client socket */ for (i = 3; i < 256; i++) { if (i != srv->errorlog_fd) close(i); } /* exec the cgi */ execve(args[0], args, env.ptr); /* most log files may have been closed/redirected by this point, * though stderr might still point to lighttpd.breakage.log */ perror(args[0]); _exit(1); } case -1: /* error */ log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed:", strerror(errno)); close(from_cgi_fds[0]); close(from_cgi_fds[1]); close(to_cgi_fds[0]); close(to_cgi_fds[1]); return -1; default: { /* parent process */ close(from_cgi_fds[1]); close(to_cgi_fds[0]); /* register PID and wait for them asynchronously */ hctx->pid = pid; hctx->fd = from_cgi_fds[0]; hctx->fde_ndx = -1; ++srv->cur_fds; if (0 == con->request.content_length) { close(to_cgi_fds[1]); } else { /* there is content to send */ if (-1 == fdevent_fcntl_set_nb(srv->ev, to_cgi_fds[1])) { log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno)); close(to_cgi_fds[1]); cgi_connection_close(srv, hctx); return -1; } if (0 != cgi_write_request(srv, hctx, to_cgi_fds[1])) { close(to_cgi_fds[1]); cgi_connection_close(srv, hctx); return -1; } ++srv->cur_fds; } fdevent_register(srv->ev, hctx->fd, cgi_handle_fdevent, hctx); if (-1 == fdevent_fcntl_set_nb(srv->ev, hctx->fd)) { log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno)); cgi_connection_close(srv, hctx); return -1; } fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); break; } } return 0; #else return -1; #endif }
static int cgi_create_env(server *srv, connection *con, plugin_data *p, buffer *cgi_handler) { pid_t pid; int to_cgi_fds[2]; int from_cgi_fds[2]; int from_cgi_err_fds[2]; struct stat st; #ifndef _WIN32 if (cgi_handler && cgi_handler->used > 1) { /* stat the exec file */ if (-1 == (stat(cgi_handler->ptr, &st))) { log_error_write(srv, __FILE__, __LINE__, "sbss", "stat for cgi-handler", cgi_handler, "failed:", strerror(errno)); return -1; } } if (pipe(to_cgi_fds)) { log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed:", strerror(errno)); return -1; } if (pipe(from_cgi_fds)) { close(to_cgi_fds[0]); close(to_cgi_fds[1]); log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed:", strerror(errno)); return -1; } if (pipe(from_cgi_err_fds)) { close(to_cgi_fds[0]); close(to_cgi_fds[1]); close(from_cgi_fds[0]); close(from_cgi_fds[1]); log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed:", strerror(errno)); return -1; } /* fork, execve */ switch (pid = fork()) { case 0: { /* child */ char **args; int argc; int i = 0; char buf[32]; size_t n; char_array env; char *c; const char *s; server_socket *srv_sock = con->srv_socket; /* move stdout to from_cgi_fd[1] */ close(STDOUT_FILENO); dup2(from_cgi_fds[1], STDOUT_FILENO); close(from_cgi_fds[1]); /* not needed */ close(from_cgi_fds[0]); /* move stderr to from_cgi_err_fd[1] */ close(STDERR_FILENO); dup2(from_cgi_err_fds[1], STDERR_FILENO); close(from_cgi_err_fds[1]); /* not needed */ close(from_cgi_err_fds[0]); /* move the stdin to to_cgi_fd[0] */ close(STDIN_FILENO); dup2(to_cgi_fds[0], STDIN_FILENO); close(to_cgi_fds[0]); /* not needed */ close(to_cgi_fds[1]); /* create environment */ env.ptr = NULL; env.size = 0; env.used = 0; cgi_env_add(&env, CONST_STR_LEN("SERVER_SOFTWARE"), CONST_STR_LEN(PACKAGE_NAME"/"PACKAGE_VERSION)); s = sock_addr_to_p(srv, &srv_sock->addr); cgi_env_add(&env, CONST_STR_LEN("SERVER_ADDR"), s, strlen(s)); /* !!! careful: s maybe reused for SERVER_NAME !!! */ if (!buffer_is_empty(con->server_name)) { size_t len = con->server_name->used - 1; char *colon = strchr(con->server_name->ptr, ':'); if (colon) len = colon - con->server_name->ptr; cgi_env_add(&env, CONST_STR_LEN("SERVER_NAME"), con->server_name->ptr, len); } else { /* use SERVER_ADDR */ cgi_env_add(&env, CONST_STR_LEN("SERVER_NAME"), s, strlen(s)); } cgi_env_add(&env, CONST_STR_LEN("GATEWAY_INTERFACE"), CONST_STR_LEN("CGI/1.1")); s = get_http_version_name(con->request.http_version); cgi_env_add(&env, CONST_STR_LEN("SERVER_PROTOCOL"), s, strlen(s)); LI_ltostr(buf, sock_addr_get_port(&srv_sock->addr)); cgi_env_add(&env, CONST_STR_LEN("SERVER_PORT"), buf, strlen(buf)); s = get_http_method_name(con->request.http_method); cgi_env_add(&env, CONST_STR_LEN("REQUEST_METHOD"), s, strlen(s)); if (!buffer_is_empty(con->request.pathinfo)) { cgi_env_add(&env, CONST_STR_LEN("PATH_INFO"), CONST_BUF_LEN(con->request.pathinfo)); } cgi_env_add(&env, CONST_STR_LEN("REDIRECT_STATUS"), CONST_STR_LEN("200")); if (!buffer_is_empty(con->uri.query)) { cgi_env_add(&env, CONST_STR_LEN("QUERY_STRING"), CONST_BUF_LEN(con->uri.query)); } else { /* set a empty QUERY_STRING */ cgi_env_add(&env, CONST_STR_LEN("QUERY_STRING"), CONST_STR_LEN("")); } if (!buffer_is_empty(con->request.orig_uri)) { cgi_env_add(&env, CONST_STR_LEN("REQUEST_URI"), CONST_BUF_LEN(con->request.orig_uri)); } s = sock_addr_to_p(srv, &con->dst_addr); cgi_env_add(&env, CONST_STR_LEN("REMOTE_ADDR"), s, strlen(s)); LI_ltostr(buf, sock_addr_get_port(&con->dst_addr)); cgi_env_add(&env, CONST_STR_LEN("REMOTE_PORT"), buf, strlen(buf)); if (!buffer_is_empty(con->authed_user)) { cgi_env_add(&env, CONST_STR_LEN("REMOTE_USER"), CONST_BUF_LEN(con->authed_user)); } #ifdef USE_OPENSSL if (srv_sock->is_ssl) { cgi_env_add(&env, CONST_STR_LEN("HTTPS"), CONST_STR_LEN("on")); } #endif /* request.content_length < SSIZE_MAX, see request.c */ if (con->request.content_length > 0) { LI_ltostr(buf, con->request.content_length); cgi_env_add(&env, CONST_STR_LEN("CONTENT_LENGTH"), buf, strlen(buf)); } cgi_env_add(&env, CONST_STR_LEN("SCRIPT_FILENAME"), CONST_BUF_LEN(con->physical.path)); cgi_env_add(&env, CONST_STR_LEN("SCRIPT_NAME"), CONST_BUF_LEN(con->uri.path)); cgi_env_add(&env, CONST_STR_LEN("DOCUMENT_ROOT"), CONST_BUF_LEN(con->physical.doc_root)); /* for valgrind */ if (NULL != (s = getenv("LD_PRELOAD"))) { cgi_env_add(&env, CONST_STR_LEN("LD_PRELOAD"), s, strlen(s)); } if (NULL != (s = getenv("LD_LIBRARY_PATH"))) { cgi_env_add(&env, CONST_STR_LEN("LD_LIBRARY_PATH"), s, strlen(s)); } #ifdef __CYGWIN__ /* CYGWIN needs SYSTEMROOT */ if (NULL != (s = getenv("SYSTEMROOT"))) { cgi_env_add(&env, CONST_STR_LEN("SYSTEMROOT"), s, strlen(s)); } #endif for (n = 0; n < con->request.headers->used; n++) { data_string *ds; ds = (data_string *)con->request.headers->data[n]; if (ds->value->used && ds->key->used) { size_t j; buffer_reset(p->tmp_buf); if (0 != strcasecmp(ds->key->ptr, "CONTENT-TYPE")) { buffer_copy_string_len(p->tmp_buf, CONST_STR_LEN("HTTP_")); p->tmp_buf->used--; /* strip \0 after HTTP_ */ } buffer_prepare_append(p->tmp_buf, ds->key->used + 2); for (j = 0; j < ds->key->used - 1; j++) { char cr = '_'; if (light_isalpha(ds->key->ptr[j])) { /* upper-case */ cr = ds->key->ptr[j] & ~32; } else if (light_isdigit(ds->key->ptr[j])) { /* copy */ cr = ds->key->ptr[j]; } p->tmp_buf->ptr[p->tmp_buf->used++] = cr; } p->tmp_buf->ptr[p->tmp_buf->used++] = '\0'; cgi_env_add(&env, CONST_BUF_LEN(p->tmp_buf), CONST_BUF_LEN(ds->value)); } } for (n = 0; n < con->environment->used; n++) { data_string *ds; ds = (data_string *)con->environment->data[n]; if (ds->value->used && ds->key->used) { size_t j; buffer_reset(p->tmp_buf); buffer_prepare_append(p->tmp_buf, ds->key->used + 2); for (j = 0; j < ds->key->used - 1; j++) { char cr = '_'; if (light_isalpha(ds->key->ptr[j])) { /* upper-case */ cr = ds->key->ptr[j] & ~32; } else if (light_isdigit(ds->key->ptr[j])) { /* copy */ cr = ds->key->ptr[j]; } p->tmp_buf->ptr[p->tmp_buf->used++] = cr; } p->tmp_buf->ptr[p->tmp_buf->used++] = '\0'; cgi_env_add(&env, CONST_BUF_LEN(p->tmp_buf), CONST_BUF_LEN(ds->value)); } } if (env.size == env.used) { env.size += 16; env.ptr = realloc(env.ptr, env.size * sizeof(*env.ptr)); } env.ptr[env.used] = NULL; /* set up args */ argc = 3; args = malloc(sizeof(*args) * argc); i = 0; if (cgi_handler && cgi_handler->used > 1) { args[i++] = cgi_handler->ptr; } args[i++] = con->physical.path->ptr; args[i++] = NULL; /* search for the last / */ if (NULL != (c = strrchr(con->physical.path->ptr, '/'))) { *c = '\0'; /* change to the physical directory */ if (-1 == chdir(con->physical.path->ptr)) { log_error_write(srv, __FILE__, __LINE__, "ssb", "chdir failed:", strerror(errno), con->physical.path); } *c = '/'; } /* we don't need the client socket */ for (i = 3; i < 256; i++) { close(i); } /* exec the cgi */ execve(args[0], args, env.ptr); /* */ SEGFAULT("execve(%s) failed: %s", args[0], strerror(errno)); break; } case -1: /* error */ ERROR("fork() failed: %s", strerror(errno)); close(to_cgi_fds[0]); close(to_cgi_fds[1]); close(from_cgi_fds[0]); close(from_cgi_fds[1]); close(from_cgi_err_fds[0]); close(from_cgi_err_fds[1]); return -1; break; default: { cgi_session *sess; /* father */ close(from_cgi_fds[1]); close(from_cgi_err_fds[1]); close(to_cgi_fds[0]); /* register PID and wait for them asyncronously */ con->mode = p->id; buffer_reset(con->physical.path); sess = cgi_session_init(); sess->remote_con = con; sess->pid = pid; assert(sess->sock); sess->sock->fd = from_cgi_fds[0]; sess->sock->type = IOSOCKET_TYPE_PIPE; sess->sock_err->fd = from_cgi_err_fds[0]; sess->sock_err->type = IOSOCKET_TYPE_PIPE; sess->wb_sock->fd = to_cgi_fds[1]; sess->wb_sock->type = IOSOCKET_TYPE_PIPE; if (-1 == fdevent_fcntl_set(srv->ev, sess->sock)) { log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno)); cgi_session_free(sess); return -1; } if (-1 == fdevent_fcntl_set(srv->ev, sess->sock_err)) { log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno)); cgi_session_free(sess); return -1; } con->plugin_ctx[p->id] = sess; fdevent_register(srv->ev, sess->sock, cgi_handle_fdevent, sess); fdevent_event_add(srv->ev, sess->sock, FDEVENT_IN); fdevent_register(srv->ev, sess->sock_err, cgi_handle_err_fdevent, sess); fdevent_event_add(srv->ev, sess->sock_err, FDEVENT_IN); sess->state = CGI_STATE_READ_RESPONSE_HEADER; break; } } return 0; #else return -1; #endif }
static int cgi_create_env(server *srv, connection *con, plugin_data *p, buffer *cgi_handler) { pid_t pid; #ifdef HAVE_IPV6 char b2[INET6_ADDRSTRLEN + 1]; #endif int to_cgi_fds[2]; int from_cgi_fds[2]; struct stat st; #ifndef __WIN32 if (cgi_handler->used > 1) { /* stat the exec file */ if (-1 == (stat(cgi_handler->ptr, &st))) { log_error_write(srv, __FILE__, __LINE__, "sbss", "stat for cgi-handler", cgi_handler, "failed:", strerror(errno)); return -1; } } if (pipe(to_cgi_fds)) { log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed:", strerror(errno)); return -1; } if (pipe(from_cgi_fds)) { log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed:", strerror(errno)); return -1; } /* fork, execve */ switch (pid = fork()) { case 0: { /* child */ char **args; int argc; int i = 0; char buf[32]; size_t n; char_array env; char *c; const char *s; server_socket *srv_sock = con->srv_socket; /* move stdout to from_cgi_fd[1] */ close(STDOUT_FILENO); dup2(from_cgi_fds[1], STDOUT_FILENO); close(from_cgi_fds[1]); /* not needed */ close(from_cgi_fds[0]); /* move the stdin to to_cgi_fd[0] */ close(STDIN_FILENO); dup2(to_cgi_fds[0], STDIN_FILENO); close(to_cgi_fds[0]); /* not needed */ close(to_cgi_fds[1]); /* HACK: * this is not nice, but it works * * we feed the stderr of the CGI to our errorlog, if possible */ if (srv->errorlog_mode == ERRORLOG_FILE) { close(STDERR_FILENO); dup2(srv->errorlog_fd, STDERR_FILENO); } /* create environment */ env.ptr = NULL; env.size = 0; env.used = 0; cgi_env_add(&env, CONST_STR_LEN("SERVER_SOFTWARE"), CONST_STR_LEN(PACKAGE_NAME"/"PACKAGE_VERSION)); if (!buffer_is_empty(con->server_name)) { cgi_env_add(&env, CONST_STR_LEN("SERVER_NAME"), CONST_BUF_LEN(con->server_name)); } else { #ifdef HAVE_IPV6 s = inet_ntop(srv_sock->addr.plain.sa_family, srv_sock->addr.plain.sa_family == AF_INET6 ? (const void *) &(srv_sock->addr.ipv6.sin6_addr) : (const void *) &(srv_sock->addr.ipv4.sin_addr), b2, sizeof(b2)-1); #else s = inet_ntoa(srv_sock->addr.ipv4.sin_addr); #endif cgi_env_add(&env, CONST_STR_LEN("SERVER_NAME"), s, strlen(s)); } cgi_env_add(&env, CONST_STR_LEN("GATEWAY_INTERFACE"), CONST_STR_LEN("CGI/1.1")); s = get_http_version_name(con->request.http_version); cgi_env_add(&env, CONST_STR_LEN("SERVER_PROTOCOL"), s, strlen(s)); ltostr(buf, #ifdef HAVE_IPV6 ntohs(srv_sock->addr.plain.sa_family == AF_INET6 ? srv_sock->addr.ipv6.sin6_port : srv_sock->addr.ipv4.sin_port) #else ntohs(srv_sock->addr.ipv4.sin_port) #endif ); cgi_env_add(&env, CONST_STR_LEN("SERVER_PORT"), buf, strlen(buf)); #ifdef HAVE_IPV6 s = inet_ntop(srv_sock->addr.plain.sa_family, srv_sock->addr.plain.sa_family == AF_INET6 ? (const void *) &(srv_sock->addr.ipv6.sin6_addr) : (const void *) &(srv_sock->addr.ipv4.sin_addr), b2, sizeof(b2)-1); #else s = inet_ntoa(srv_sock->addr.ipv4.sin_addr); #endif cgi_env_add(&env, CONST_STR_LEN("SERVER_ADDR"), s, strlen(s)); s = get_http_method_name(con->request.http_method); cgi_env_add(&env, CONST_STR_LEN("REQUEST_METHOD"), s, strlen(s)); if (!buffer_is_empty(con->request.pathinfo)) { cgi_env_add(&env, CONST_STR_LEN("PATH_INFO"), CONST_BUF_LEN(con->request.pathinfo)); } cgi_env_add(&env, CONST_STR_LEN("REDIRECT_STATUS"), CONST_STR_LEN("200")); if (!buffer_is_empty(con->uri.query)) { cgi_env_add(&env, CONST_STR_LEN("QUERY_STRING"), CONST_BUF_LEN(con->uri.query)); } if (!buffer_is_empty(con->request.orig_uri)) { cgi_env_add(&env, CONST_STR_LEN("REQUEST_URI"), CONST_BUF_LEN(con->request.orig_uri)); } #ifdef HAVE_IPV6 s = inet_ntop(con->dst_addr.plain.sa_family, con->dst_addr.plain.sa_family == AF_INET6 ? (const void *) &(con->dst_addr.ipv6.sin6_addr) : (const void *) &(con->dst_addr.ipv4.sin_addr), b2, sizeof(b2)-1); #else s = inet_ntoa(con->dst_addr.ipv4.sin_addr); #endif cgi_env_add(&env, CONST_STR_LEN("REMOTE_ADDR"), s, strlen(s)); ltostr(buf, #ifdef HAVE_IPV6 ntohs(con->dst_addr.plain.sa_family == AF_INET6 ? con->dst_addr.ipv6.sin6_port : con->dst_addr.ipv4.sin_port) #else ntohs(con->dst_addr.ipv4.sin_port) #endif ); cgi_env_add(&env, CONST_STR_LEN("REMOTE_PORT"), buf, strlen(buf)); if (!buffer_is_empty(con->authed_user)) { cgi_env_add(&env, CONST_STR_LEN("REMOTE_USER"), CONST_BUF_LEN(con->authed_user)); } /* request.content_length < SSIZE_MAX, see request.c */ ltostr(buf, con->request.content_length); cgi_env_add(&env, CONST_STR_LEN("CONTENT_LENGTH"), buf, strlen(buf)); cgi_env_add(&env, CONST_STR_LEN("SCRIPT_FILENAME"), CONST_BUF_LEN(con->physical.path)); cgi_env_add(&env, CONST_STR_LEN("SCRIPT_NAME"), CONST_BUF_LEN(con->uri.path)); cgi_env_add(&env, CONST_STR_LEN("DOCUMENT_ROOT"), CONST_BUF_LEN(con->physical.doc_root)); /* for valgrind */ if (NULL != (s = getenv("LD_PRELOAD"))) { cgi_env_add(&env, CONST_STR_LEN("LD_PRELOAD"), s, strlen(s)); } if (NULL != (s = getenv("LD_LIBRARY_PATH"))) { cgi_env_add(&env, CONST_STR_LEN("LD_LIBRARY_PATH"), s, strlen(s)); } #ifdef __CYGWIN__ /* CYGWIN needs SYSTEMROOT */ if (NULL != (s = getenv("SYSTEMROOT"))) { cgi_env_add(&env, CONST_STR_LEN("SYSTEMROOT"), s, strlen(s)); } #endif for (n = 0; n < con->request.headers->used; n++) { data_string *ds; ds = (data_string *)con->request.headers->data[n]; if (ds->value->used && ds->key->used) { size_t j; buffer_reset(p->tmp_buf); if (0 != strcasecmp(ds->key->ptr, "CONTENT-TYPE")) { buffer_copy_string(p->tmp_buf, "HTTP_"); p->tmp_buf->used--; /* strip \0 after HTTP_ */ } buffer_prepare_append(p->tmp_buf, ds->key->used + 2); for (j = 0; j < ds->key->used - 1; j++) { char cr = '_'; if (light_isalpha(ds->key->ptr[j])) { /* upper-case */ cr = ds->key->ptr[j] & ~32; } else if (light_isdigit(ds->key->ptr[j])) { /* copy */ cr = ds->key->ptr[j]; } p->tmp_buf->ptr[p->tmp_buf->used++] = cr; } p->tmp_buf->ptr[p->tmp_buf->used++] = '\0'; cgi_env_add(&env, CONST_BUF_LEN(p->tmp_buf), CONST_BUF_LEN(ds->value)); } } for (n = 0; n < con->environment->used; n++) { data_string *ds; ds = (data_string *)con->environment->data[n]; if (ds->value->used && ds->key->used) { size_t j; buffer_reset(p->tmp_buf); buffer_prepare_append(p->tmp_buf, ds->key->used + 2); for (j = 0; j < ds->key->used - 1; j++) { p->tmp_buf->ptr[p->tmp_buf->used++] = isalpha((unsigned char)ds->key->ptr[j]) ? toupper((unsigned char)ds->key->ptr[j]) : '_'; } p->tmp_buf->ptr[p->tmp_buf->used++] = '\0'; cgi_env_add(&env, CONST_BUF_LEN(p->tmp_buf), CONST_BUF_LEN(ds->value)); } } if (env.size == env.used) { env.size += 16; env.ptr = realloc(env.ptr, env.size * sizeof(*env.ptr)); } env.ptr[env.used] = NULL; /* set up args */ argc = 3; args = malloc(sizeof(*args) * argc); i = 0; if (cgi_handler->used > 1) { args[i++] = cgi_handler->ptr; } args[i++] = con->physical.path->ptr; args[i++] = NULL; /* search for the last / */ if (NULL != (c = strrchr(con->physical.path->ptr, '/'))) { *c = '\0'; /* change to the physical directory */ if (-1 == chdir(con->physical.path->ptr)) { log_error_write(srv, __FILE__, __LINE__, "ssb", "chdir failed:", strerror(errno), con->physical.path); } *c = '/'; } /* we don't need the client socket */ for (i = 3; i < 256; i++) { if (i != srv->errorlog_fd) close(i); } /* exec the cgi */ execve(args[0], args, env.ptr); log_error_write(srv, __FILE__, __LINE__, "sss", "CGI failed:", strerror(errno), args[0]); /* */ SEGFAULT(); break; } case -1: /* error */ log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed:", strerror(errno)); break; default: { handler_ctx *hctx; /* father */ close(from_cgi_fds[1]); close(to_cgi_fds[0]); if (con->request.content_length) { chunkqueue *cq = con->request_content_queue; chunk *c; assert(chunkqueue_length(cq) == (off_t)con->request.content_length); /* there is content to send */ for (c = cq->first; c; c = cq->first) { int r = 0; /* copy all chunks */ switch(c->type) { case FILE_CHUNK: if (c->file.mmap.start == MAP_FAILED) { if (-1 == c->file.fd && /* open the file if not already open */ -1 == (c->file.fd = open(c->file.name->ptr, O_RDONLY))) { log_error_write(srv, __FILE__, __LINE__, "ss", "open failed: ", strerror(errno)); close(from_cgi_fds[0]); close(to_cgi_fds[1]); return -1; } c->file.mmap.length = c->file.length; if (MAP_FAILED == (c->file.mmap.start = mmap(0, c->file.mmap.length, PROT_READ, MAP_SHARED, c->file.fd, 0))) { log_error_write(srv, __FILE__, __LINE__, "ssbd", "mmap failed: ", strerror(errno), c->file.name, c->file.fd); close(from_cgi_fds[0]); close(to_cgi_fds[1]); return -1; } close(c->file.fd); c->file.fd = -1; /* chunk_reset() or chunk_free() will cleanup for us */ } if ((r = write(to_cgi_fds[1], c->file.mmap.start + c->offset, c->file.length - c->offset)) < 0) { switch(errno) { case ENOSPC: con->http_status = 507; break; default: con->http_status = 403; break; } } break; case MEM_CHUNK: if ((r = write(to_cgi_fds[1], c->mem->ptr + c->offset, c->mem->used - c->offset - 1)) < 0) { switch(errno) { case ENOSPC: con->http_status = 507; break; default: con->http_status = 403; break; } } break; case UNUSED_CHUNK: break; } if (r > 0) { c->offset += r; cq->bytes_out += r; } else { break; } chunkqueue_remove_finished_chunks(cq); } } close(to_cgi_fds[1]); /* register PID and wait for them asyncronously */ con->mode = p->id; buffer_reset(con->physical.path); hctx = cgi_handler_ctx_init(); hctx->remote_conn = con; hctx->plugin_data = p; hctx->pid = pid; hctx->fd = from_cgi_fds[0]; hctx->fde_ndx = -1; con->plugin_ctx[p->id] = hctx; fdevent_register(srv->ev, hctx->fd, cgi_handle_fdevent, hctx); fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); if (-1 == fdevent_fcntl_set(srv->ev, hctx->fd)) { log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno)); fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd); fdevent_unregister(srv->ev, hctx->fd); log_error_write(srv, __FILE__, __LINE__, "sd", "cgi close:", hctx->fd); close(hctx->fd); cgi_handler_ctx_free(hctx); con->plugin_ctx[p->id] = NULL; return -1; } break; } } return 0; #else return -1; #endif }
static int cgi_create_env(server *srv, connection *con, plugin_data *p, handler_ctx *hctx, buffer *cgi_handler) { char *args[3]; int to_cgi_fds[2]; int from_cgi_fds[2]; int dfd = -1; UNUSED(p); if (!buffer_string_is_empty(cgi_handler)) { if (NULL == cgi_stat(srv, con, cgi_handler)) { log_error_write(srv, __FILE__, __LINE__, "sbss", "stat for cgi-handler", cgi_handler, "failed:", strerror(errno)); return -1; } } if (pipe_cloexec(to_cgi_fds)) { log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed:", strerror(errno)); return -1; } if (pipe_cloexec(from_cgi_fds)) { close(to_cgi_fds[0]); close(to_cgi_fds[1]); log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed:", strerror(errno)); return -1; } fdevent_setfd_cloexec(to_cgi_fds[1]); fdevent_setfd_cloexec(from_cgi_fds[0]); { size_t i = 0; http_cgi_opts opts = { 0, 0, NULL, NULL }; env_accum *env = &p->env; env->used = 0; env->oused = 0; /* create environment */ http_cgi_headers(srv, con, &opts, cgi_env_add, env); /* for valgrind */ if (p->env.ld_preload) { cgi_env_add(env, CONST_STR_LEN("LD_PRELOAD"), CONST_BUF_LEN(p->env.ld_preload)); } if (p->env.ld_library_path) { cgi_env_add(env, CONST_STR_LEN("LD_LIBRARY_PATH"), CONST_BUF_LEN(p->env.ld_library_path)); } #ifdef __CYGWIN__ /* CYGWIN needs SYSTEMROOT */ if (p->env.systemroot) { cgi_env_add(env, CONST_STR_LEN("SYSTEMROOT"), CONST_BUF_LEN(p->env.systemroot)); } #endif if (env->esize <= env->oused) { env->esize = (env->oused + 1 + 0xf) & ~(0xfuL); env->eptr = realloc(env->eptr, env->esize * sizeof(*env->eptr)); force_assert(env->eptr); } for (i = 0; i < env->oused; ++i) { env->eptr[i] = env->ptr + env->offsets[i]; } env->eptr[env->oused] = NULL; /* set up args */ i = 0; if (!buffer_string_is_empty(cgi_handler)) { args[i++] = cgi_handler->ptr; } args[i++] = con->physical.path->ptr; args[i ] = NULL; } dfd = fdevent_open_dirname(con->physical.path->ptr, con->conf.follow_symlink); if (-1 == dfd) { log_error_write(srv, __FILE__, __LINE__, "ssb", "open dirname failed:", strerror(errno), con->physical.path); } hctx->pid = (dfd >= 0) ? fdevent_fork_execve(args[0], args, p->env.eptr, to_cgi_fds[0], from_cgi_fds[1], -1, dfd) : -1; if (-1 == hctx->pid) { /* log error with errno prior to calling close() (might change errno) */ log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed:", strerror(errno)); if (-1 != dfd) close(dfd); close(from_cgi_fds[0]); close(from_cgi_fds[1]); close(to_cgi_fds[0]); close(to_cgi_fds[1]); return -1; } else { if (-1 != dfd) close(dfd); close(from_cgi_fds[1]); close(to_cgi_fds[0]); hctx->fd = from_cgi_fds[0]; ++srv->cur_fds; cgi_pid_add(p, hctx->pid, hctx); if (0 == con->request.content_length) { close(to_cgi_fds[1]); } else { /* there is content to send */ if (-1 == fdevent_fcntl_set_nb(srv->ev, to_cgi_fds[1])) { log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno)); close(to_cgi_fds[1]); cgi_connection_close(srv, hctx); return -1; } if (0 != cgi_write_request(srv, hctx, to_cgi_fds[1])) { close(to_cgi_fds[1]); cgi_connection_close(srv, hctx); return -1; } ++srv->cur_fds; } hctx->fdn = fdevent_register(srv->ev, hctx->fd, cgi_handle_fdevent, hctx); if (-1 == fdevent_fcntl_set_nb(srv->ev, hctx->fd)) { log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno)); cgi_connection_close(srv, hctx); return -1; } fdevent_fdnode_event_set(srv->ev, hctx->fdn, FDEVENT_IN | FDEVENT_RDHUP); return 0; } }