1 #include "first.h" 2 3 #include <sys/types.h> 4 #include <limits.h> 5 #include <stdlib.h> 6 #include <string.h> 7 8 #include "gw_backend.h" 9 typedef gw_plugin_config plugin_config; 10 typedef gw_plugin_data plugin_data; 11 typedef gw_handler_ctx handler_ctx; 12 13 #include "base.h" 14 #include "buffer.h" 15 #include "http_cgi.h" 16 #include "log.h" 17 18 enum { LI_PROTOCOL_SCGI, LI_PROTOCOL_UWSGI }; 19 20 static void mod_scgi_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) { 21 switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */ 22 case 0: /* scgi.server */ 23 if (cpv->vtype == T_CONFIG_LOCAL) { 24 gw_plugin_config * const gw = cpv->v.v; 25 pconf->exts = gw->exts; 26 pconf->exts_auth = gw->exts_auth; 27 pconf->exts_resp = gw->exts_resp; 28 } 29 break; 30 case 1: /* scgi.balance */ 31 /*if (cpv->vtype == T_CONFIG_LOCAL)*//*always true here for this param*/ 32 pconf->balance = (int)cpv->v.u; 33 break; 34 case 2: /* scgi.debug */ 35 pconf->debug = (int)cpv->v.u; 36 break; 37 case 3: /* scgi.map-extensions */ 38 pconf->ext_mapping = cpv->v.a; 39 break; 40 case 4: /* scgi.protocol */ 41 /*if (cpv->vtype == T_CONFIG_LOCAL)*//*always true here for this param*/ 42 pconf->proto = (int)cpv->v.u; 43 break; 44 default:/* should not happen */ 45 return; 46 } 47 } 48 49 static void mod_scgi_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) { 50 do { 51 mod_scgi_merge_config_cpv(pconf, cpv); 52 } while ((++cpv)->k_id != -1); 53 } 54 55 static void mod_scgi_patch_config(request_st * const r, plugin_data * const p) { 56 memcpy(&p->conf, &p->defaults, sizeof(plugin_config)); 57 for (int i = 1, used = p->nconfig; i < used; ++i) { 58 if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id)) 59 mod_scgi_merge_config(&p->conf, p->cvlist + p->cvlist[i].v.u2[0]); 60 } 61 } 62 63 SETDEFAULTS_FUNC(mod_scgi_set_defaults) { 64 static const config_plugin_keys_t cpk[] = { 65 { CONST_STR_LEN("scgi.server"), 66 T_CONFIG_ARRAY_KVARRAY, 67 T_CONFIG_SCOPE_CONNECTION } 68 ,{ CONST_STR_LEN("scgi.balance"), 69 T_CONFIG_STRING, 70 T_CONFIG_SCOPE_CONNECTION } 71 ,{ CONST_STR_LEN("scgi.debug"), 72 T_CONFIG_INT, 73 T_CONFIG_SCOPE_CONNECTION } 74 ,{ CONST_STR_LEN("scgi.map-extensions"), 75 T_CONFIG_ARRAY_KVSTRING, 76 T_CONFIG_SCOPE_CONNECTION } 77 ,{ CONST_STR_LEN("scgi.protocol"), 78 T_CONFIG_STRING, 79 T_CONFIG_SCOPE_CONNECTION } 80 ,{ NULL, 0, 81 T_CONFIG_UNSET, 82 T_CONFIG_SCOPE_UNSET } 83 }; 84 85 plugin_data * const p = p_d; 86 if (!config_plugin_values_init(srv, p, cpk, "mod_scgi")) 87 return HANDLER_ERROR; 88 89 /* process and validate config directives 90 * (init i to 0 if global context; to 1 to skip empty global context) */ 91 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) { 92 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 93 for (; -1 != cpv->k_id; ++cpv) { 94 switch (cpv->k_id) { 95 case 0:{/* scgi.server */ 96 gw_plugin_config *gw = ck_calloc(1, sizeof(gw_plugin_config)); 97 if (!gw_set_defaults_backend(srv, p, cpv->v.a, gw, 1, 98 cpk[cpv->k_id].k)) { 99 gw_plugin_config_free(gw); 100 return HANDLER_ERROR; 101 } 102 cpv->v.v = gw; 103 cpv->vtype = T_CONFIG_LOCAL; 104 break; 105 } 106 case 1: /* scgi.balance */ 107 cpv->v.u = (unsigned int)gw_get_defaults_balance(srv, cpv->v.b); 108 break; 109 case 2: /* scgi.debug */ 110 case 3: /* scgi.map-extensions */ 111 break; 112 case 4: /* scgi.protocol */ 113 if (buffer_eq_slen(cpv->v.b, CONST_STR_LEN("scgi"))) 114 cpv->v.u = LI_PROTOCOL_SCGI; 115 else if (buffer_eq_slen(cpv->v.b, CONST_STR_LEN("uwsgi"))) 116 cpv->v.u = LI_PROTOCOL_UWSGI; 117 else { 118 log_error(srv->errh, __FILE__, __LINE__, 119 "unexpected type for key: %s" 120 "expected \"scgi\" or \"uwsgi\"", cpk[cpv->k_id].k); 121 return HANDLER_ERROR; 122 } 123 break; 124 default:/* should not happen */ 125 break; 126 } 127 } 128 } 129 130 /* default is 0 */ 131 /*p->defaults.balance = (unsigned int)gw_get_defaults_balance(srv, NULL);*/ 132 /*p->defaults.proto = LI_PROTOCOL_SCGI;*//*(default)*/ 133 134 /* initialize p->defaults from global config context */ 135 if (p->nconfig > 0 && p->cvlist->v.u2[1]) { 136 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0]; 137 if (-1 != cpv->k_id) 138 mod_scgi_merge_config(&p->defaults, cpv); 139 } 140 141 return HANDLER_GO_ON; 142 } 143 144 static int scgi_env_add_scgi(void *venv, const char *key, size_t key_len, const char *val, size_t val_len) { 145 buffer *env = venv; 146 size_t len; 147 148 if (!key || (!val && val_len)) return -1; 149 150 len = key_len + val_len + 2; 151 152 char *dst = buffer_extend(env, len); 153 memcpy(dst, key, key_len); 154 dst[key_len] = '\0'; 155 dst += key_len + 1; 156 memcpy(dst, val, val_len); 157 dst[val_len] = '\0'; 158 159 return 0; 160 } 161 162 163 static int scgi_env_add_uwsgi(void *venv, const char *key, size_t key_len, const char *val, size_t val_len) { 164 if (!key || (!val && val_len)) return -1; 165 if (key_len > USHRT_MAX || val_len > USHRT_MAX) return -1; 166 167 char *dst = buffer_extend(venv, 2 + key_len + 2 + val_len); 168 dst[0] = key_len & 0xff; /* little-endian */ 169 dst[1] = (key_len >> 8) & 0xff; 170 memcpy(dst + 2, key, key_len); 171 dst += 2+key_len; 172 dst[0] = val_len & 0xff; /* little-endian */ 173 dst[1] = (val_len >> 8) & 0xff; 174 memcpy(dst + 2, val, val_len); 175 176 return 0; 177 } 178 179 180 static handler_t scgi_create_env(handler_ctx *hctx) { 181 gw_host *host = hctx->host; 182 request_st * const r = hctx->r; 183 http_cgi_opts opts = { 0, 0, host->docroot, NULL }; 184 http_cgi_header_append_cb scgi_env_add = hctx->conf.proto == LI_PROTOCOL_SCGI 185 ? scgi_env_add_scgi 186 : scgi_env_add_uwsgi; 187 size_t offset; 188 size_t rsz = (size_t)(r->read_queue.bytes_out - hctx->wb.bytes_in); 189 if (rsz >= 65536) rsz = r->rqst_header_len; 190 buffer * const b = chunkqueue_prepend_buffer_open_sz(&hctx->wb, rsz); 191 192 /* save space for 9 digits (plus ':'), though incoming HTTP request 193 * currently limited to 64k (65535, so 5 chars) */ 194 buffer_copy_string_len(b, CONST_STR_LEN(" ")); 195 196 if (0 != http_cgi_headers(r, &opts, scgi_env_add, b)) { 197 r->http_status = 400; 198 r->handler_module = NULL; 199 buffer_clear(b); 200 chunkqueue_remove_finished_chunks(&hctx->wb); 201 return HANDLER_FINISHED; 202 } 203 204 if (hctx->conf.proto == LI_PROTOCOL_SCGI) { 205 buffer * const tb = r->tmp_buf; 206 size_t len; 207 scgi_env_add(b, CONST_STR_LEN("SCGI"), CONST_STR_LEN("1")); 208 buffer_clear(tb); 209 buffer_append_int(tb, buffer_clen(b)-10); 210 buffer_append_char(tb, ':'); 211 len = buffer_clen(tb); 212 offset = 10 - len; 213 memcpy(b->ptr+offset, tb->ptr, len); 214 buffer_append_char(b, ','); 215 } else { /* LI_PROTOCOL_UWSGI */ 216 /* http://uwsgi-docs.readthedocs.io/en/latest/Protocol.html */ 217 size_t len = buffer_clen(b)-10; 218 if (len > USHRT_MAX) { 219 r->http_status = 431; /* Request Header Fields Too Large */ 220 r->handler_module = NULL; 221 buffer_clear(b); 222 chunkqueue_remove_finished_chunks(&hctx->wb); 223 return HANDLER_FINISHED; 224 } 225 offset = 10 - 4; 226 b->ptr[offset] = 0; 227 b->ptr[offset+1] = len & 0xff; /* little-endian */ 228 b->ptr[offset+2] = (len >> 8) & 0xff; 229 b->ptr[offset+3] = 0; 230 } 231 232 hctx->wb_reqlen = buffer_clen(b) - offset; 233 chunkqueue_prepend_buffer_commit(&hctx->wb); 234 chunkqueue_mark_written(&hctx->wb, offset); 235 hctx->wb.bytes_in -= (off_t)offset; 236 hctx->wb.bytes_out -= (off_t)offset; 237 238 if (r->reqbody_length) { 239 chunkqueue_append_chunkqueue(&hctx->wb, &r->reqbody_queue); 240 if (r->reqbody_length > 0) 241 hctx->wb_reqlen += r->reqbody_length; /* total req size */ 242 else /* as-yet-unknown total request size (Transfer-Encoding: chunked)*/ 243 hctx->wb_reqlen = -hctx->wb_reqlen; 244 } 245 246 plugin_stats_inc("scgi.requests"); 247 return HANDLER_GO_ON; 248 } 249 250 251 static handler_t scgi_check_extension(request_st * const r, void *p_d, int uri_path_handler) { 252 plugin_data *p = p_d; 253 handler_t rc; 254 255 if (NULL != r->handler_module) return HANDLER_GO_ON; 256 257 mod_scgi_patch_config(r, p); 258 if (NULL == p->conf.exts) return HANDLER_GO_ON; 259 260 rc = gw_check_extension(r, p, uri_path_handler, 0); 261 if (HANDLER_GO_ON != rc) return rc; 262 263 if (r->handler_module == p->self) { 264 handler_ctx *hctx = r->plugin_ctx[p->id]; 265 hctx->opts.backend = BACKEND_SCGI; 266 hctx->create_env = scgi_create_env; 267 hctx->response = chunk_buffer_acquire(); 268 } 269 270 return HANDLER_GO_ON; 271 } 272 273 /* uri-path handler */ 274 static handler_t scgi_check_extension_1(request_st * const r, void *p_d) { 275 return scgi_check_extension(r, p_d, 1); 276 } 277 278 /* start request handler */ 279 static handler_t scgi_check_extension_2(request_st * const r, void *p_d) { 280 return scgi_check_extension(r, p_d, 0); 281 } 282 283 284 __attribute_cold__ 285 int mod_scgi_plugin_init(plugin *p); 286 int mod_scgi_plugin_init(plugin *p) { 287 p->version = LIGHTTPD_VERSION_ID; 288 p->name = "scgi"; 289 290 p->init = gw_init; 291 p->cleanup = gw_free; 292 p->set_defaults = mod_scgi_set_defaults; 293 p->handle_request_reset = gw_handle_request_reset; 294 p->handle_uri_clean = scgi_check_extension_1; 295 p->handle_subrequest_start = scgi_check_extension_2; 296 p->handle_subrequest = gw_handle_subrequest; 297 p->handle_trigger = gw_handle_trigger; 298 p->handle_waitpid = gw_handle_waitpid_cb; 299 300 return 0; 301 } 302