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 = calloc(1, sizeof(gw_plugin_config)); 97 force_assert(gw); 98 if (!gw_set_defaults_backend(srv, p, cpv->v.a, gw, 1, 99 cpk[cpv->k_id].k)) { 100 gw_plugin_config_free(gw); 101 return HANDLER_ERROR; 102 } 103 cpv->v.v = gw; 104 cpv->vtype = T_CONFIG_LOCAL; 105 break; 106 } 107 case 1: /* scgi.balance */ 108 cpv->v.u = (unsigned int)gw_get_defaults_balance(srv, cpv->v.b); 109 break; 110 case 2: /* scgi.debug */ 111 case 3: /* scgi.map-extensions */ 112 break; 113 case 4: /* scgi.protocol */ 114 if (buffer_eq_slen(cpv->v.b, CONST_STR_LEN("scgi"))) 115 cpv->v.u = LI_PROTOCOL_SCGI; 116 else if (buffer_eq_slen(cpv->v.b, CONST_STR_LEN("uwsgi"))) 117 cpv->v.u = LI_PROTOCOL_UWSGI; 118 else { 119 log_error(srv->errh, __FILE__, __LINE__, 120 "unexpected type for key: %s" 121 "expected \"scgi\" or \"uwsgi\"", cpk[cpv->k_id].k); 122 return HANDLER_ERROR; 123 } 124 break; 125 default:/* should not happen */ 126 break; 127 } 128 } 129 } 130 131 /* default is 0 */ 132 /*p->defaults.balance = (unsigned int)gw_get_defaults_balance(srv, NULL);*/ 133 /*p->defaults.proto = LI_PROTOCOL_SCGI;*//*(default)*/ 134 135 /* initialize p->defaults from global config context */ 136 if (p->nconfig > 0 && p->cvlist->v.u2[1]) { 137 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0]; 138 if (-1 != cpv->k_id) 139 mod_scgi_merge_config(&p->defaults, cpv); 140 } 141 142 return HANDLER_GO_ON; 143 } 144 145 static int scgi_env_add_scgi(void *venv, const char *key, size_t key_len, const char *val, size_t val_len) { 146 buffer *env = venv; 147 size_t len; 148 149 if (!key || (!val && val_len)) return -1; 150 151 len = key_len + val_len + 2; 152 153 char *dst = buffer_extend(env, len); 154 memcpy(dst, key, key_len); 155 dst[key_len] = '\0'; 156 dst += key_len + 1; 157 memcpy(dst, val, val_len); 158 dst[val_len] = '\0'; 159 160 return 0; 161 } 162 163 164 static int scgi_env_add_uwsgi(void *venv, const char *key, size_t key_len, const char *val, size_t val_len) { 165 if (!key || (!val && val_len)) return -1; 166 if (key_len > USHRT_MAX || val_len > USHRT_MAX) return -1; 167 168 char *dst = buffer_extend(venv, 2 + key_len + 2 + val_len); 169 dst[0] = key_len & 0xff; /* little-endian */ 170 dst[1] = (key_len >> 8) & 0xff; 171 memcpy(dst + 2, key, key_len); 172 dst += 2+key_len; 173 dst[0] = val_len & 0xff; /* little-endian */ 174 dst[1] = (val_len >> 8) & 0xff; 175 memcpy(dst + 2, val, val_len); 176 177 return 0; 178 } 179 180 181 static handler_t scgi_create_env(handler_ctx *hctx) { 182 gw_host *host = hctx->host; 183 request_st * const r = hctx->r; 184 http_cgi_opts opts = { 0, 0, host->docroot, NULL }; 185 http_cgi_header_append_cb scgi_env_add = hctx->conf.proto == LI_PROTOCOL_SCGI 186 ? scgi_env_add_scgi 187 : scgi_env_add_uwsgi; 188 size_t offset; 189 size_t rsz = (size_t)(r->read_queue.bytes_out - hctx->wb.bytes_in); 190 if (rsz >= 65536) rsz = r->rqst_header_len; 191 buffer * const b = chunkqueue_prepend_buffer_open_sz(&hctx->wb, rsz); 192 193 /* save space for 9 digits (plus ':'), though incoming HTTP request 194 * currently limited to 64k (65535, so 5 chars) */ 195 buffer_copy_string_len(b, CONST_STR_LEN(" ")); 196 197 if (0 != http_cgi_headers(r, &opts, scgi_env_add, b)) { 198 r->http_status = 400; 199 r->handler_module = NULL; 200 buffer_clear(b); 201 chunkqueue_remove_finished_chunks(&hctx->wb); 202 return HANDLER_FINISHED; 203 } 204 205 if (hctx->conf.proto == LI_PROTOCOL_SCGI) { 206 buffer * const tb = r->tmp_buf; 207 size_t len; 208 scgi_env_add(b, CONST_STR_LEN("SCGI"), CONST_STR_LEN("1")); 209 buffer_clear(tb); 210 buffer_append_int(tb, buffer_clen(b)-10); 211 buffer_append_char(tb, ':'); 212 len = buffer_clen(tb); 213 offset = 10 - len; 214 memcpy(b->ptr+offset, tb->ptr, len); 215 buffer_append_char(b, ','); 216 } else { /* LI_PROTOCOL_UWSGI */ 217 /* http://uwsgi-docs.readthedocs.io/en/latest/Protocol.html */ 218 size_t len = buffer_clen(b)-10; 219 if (len > USHRT_MAX) { 220 r->http_status = 431; /* Request Header Fields Too Large */ 221 r->handler_module = NULL; 222 buffer_clear(b); 223 chunkqueue_remove_finished_chunks(&hctx->wb); 224 return HANDLER_FINISHED; 225 } 226 offset = 10 - 4; 227 b->ptr[offset] = 0; 228 b->ptr[offset+1] = len & 0xff; /* little-endian */ 229 b->ptr[offset+2] = (len >> 8) & 0xff; 230 b->ptr[offset+3] = 0; 231 } 232 233 hctx->wb_reqlen = buffer_clen(b) - offset; 234 chunkqueue_prepend_buffer_commit(&hctx->wb); 235 chunkqueue_mark_written(&hctx->wb, offset); 236 hctx->wb.bytes_in -= (off_t)offset; 237 hctx->wb.bytes_out -= (off_t)offset; 238 239 if (r->reqbody_length) { 240 chunkqueue_append_chunkqueue(&hctx->wb, &r->reqbody_queue); 241 if (r->reqbody_length > 0) 242 hctx->wb_reqlen += r->reqbody_length; /* total req size */ 243 else /* as-yet-unknown total request size (Transfer-Encoding: chunked)*/ 244 hctx->wb_reqlen = -hctx->wb_reqlen; 245 } 246 247 plugin_stats_inc("scgi.requests"); 248 return HANDLER_GO_ON; 249 } 250 251 252 static handler_t scgi_check_extension(request_st * const r, void *p_d, int uri_path_handler) { 253 plugin_data *p = p_d; 254 handler_t rc; 255 256 if (NULL != r->handler_module) return HANDLER_GO_ON; 257 258 mod_scgi_patch_config(r, p); 259 if (NULL == p->conf.exts) return HANDLER_GO_ON; 260 261 rc = gw_check_extension(r, p, uri_path_handler, 0); 262 if (HANDLER_GO_ON != rc) return rc; 263 264 if (r->handler_module == p->self) { 265 handler_ctx *hctx = r->plugin_ctx[p->id]; 266 hctx->opts.backend = BACKEND_SCGI; 267 hctx->create_env = scgi_create_env; 268 hctx->response = chunk_buffer_acquire(); 269 } 270 271 return HANDLER_GO_ON; 272 } 273 274 /* uri-path handler */ 275 static handler_t scgi_check_extension_1(request_st * const r, void *p_d) { 276 return scgi_check_extension(r, p_d, 1); 277 } 278 279 /* start request handler */ 280 static handler_t scgi_check_extension_2(request_st * const r, void *p_d) { 281 return scgi_check_extension(r, p_d, 0); 282 } 283 284 285 __attribute_cold__ 286 int mod_scgi_plugin_init(plugin *p); 287 int mod_scgi_plugin_init(plugin *p) { 288 p->version = LIGHTTPD_VERSION_ID; 289 p->name = "scgi"; 290 291 p->init = gw_init; 292 p->cleanup = gw_free; 293 p->set_defaults = mod_scgi_set_defaults; 294 p->handle_request_reset = gw_handle_request_reset; 295 p->handle_uri_clean = scgi_check_extension_1; 296 p->handle_subrequest_start = scgi_check_extension_2; 297 p->handle_subrequest = gw_handle_subrequest; 298 p->handle_trigger = gw_handle_trigger; 299 p->handle_waitpid = gw_handle_waitpid_cb; 300 301 return 0; 302 } 303