/src/h2o/lib/handler/configurator/fastcgi.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2015 DeNA Co., Ltd. Kazuho Oku |
3 | | * |
4 | | * Permission is hereby granted, free of charge, to any person obtaining a copy |
5 | | * of this software and associated documentation files (the "Software"), to |
6 | | * deal in the Software without restriction, including without limitation the |
7 | | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
8 | | * sell copies of the Software, and to permit persons to whom the Software is |
9 | | * furnished to do so, subject to the following conditions: |
10 | | * |
11 | | * The above copyright notice and this permission notice shall be included in |
12 | | * all copies or substantial portions of the Software. |
13 | | * |
14 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
15 | | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
16 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
17 | | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
18 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
19 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
20 | | * IN THE SOFTWARE. |
21 | | */ |
22 | | #include <arpa/inet.h> |
23 | | #include <errno.h> |
24 | | #include <fcntl.h> |
25 | | #include <inttypes.h> |
26 | | #include <netinet/in.h> |
27 | | #include <netinet/tcp.h> |
28 | | #include <pwd.h> |
29 | | #include <stdlib.h> |
30 | | #include <sys/stat.h> |
31 | | #include <sys/un.h> |
32 | | #include "h2o.h" |
33 | | #include "h2o/configurator.h" |
34 | | #include "h2o/serverutil.h" |
35 | | |
36 | | struct fastcgi_configurator_t { |
37 | | h2o_configurator_t super; |
38 | | h2o_fastcgi_config_vars_t *vars; |
39 | | h2o_fastcgi_config_vars_t _vars_stack[H2O_CONFIGURATOR_NUM_LEVELS + 1]; |
40 | | }; |
41 | | |
42 | | static int on_config_timeout_io(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node) |
43 | 0 | { |
44 | 0 | struct fastcgi_configurator_t *self = (void *)cmd->configurator; |
45 | 0 | return h2o_configurator_scanf(cmd, node, "%" SCNu64, &self->vars->io_timeout); |
46 | 0 | } |
47 | | |
48 | | static int on_config_timeout_keepalive(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node) |
49 | 0 | { |
50 | 0 | struct fastcgi_configurator_t *self = (void *)cmd->configurator; |
51 | 0 | return h2o_configurator_scanf(cmd, node, "%" SCNu64, &self->vars->keepalive_timeout); |
52 | 0 | } |
53 | | |
54 | | static int on_config_document_root(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node) |
55 | 0 | { |
56 | 0 | struct fastcgi_configurator_t *self = (void *)cmd->configurator; |
57 | |
|
58 | 0 | if (node->data.scalar[0] == '\0') { |
59 | | /* unset */ |
60 | 0 | self->vars->document_root = h2o_iovec_init(NULL, 0); |
61 | 0 | } else if (node->data.scalar[0] == '/') { |
62 | | /* set */ |
63 | 0 | self->vars->document_root = h2o_iovec_init(node->data.scalar, strlen(node->data.scalar)); |
64 | 0 | } else { |
65 | 0 | h2o_configurator_errprintf(cmd, node, "value does not start from `/`"); |
66 | 0 | return -1; |
67 | 0 | } |
68 | 0 | return 0; |
69 | 0 | } |
70 | | |
71 | | static int on_config_send_delegated_uri(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node) |
72 | 0 | { |
73 | 0 | struct fastcgi_configurator_t *self = (void *)cmd->configurator; |
74 | 0 | ssize_t v; |
75 | |
|
76 | 0 | if ((v = h2o_configurator_get_one_of(cmd, node, "OFF,ON")) == -1) |
77 | 0 | return -1; |
78 | 0 | self->vars->send_delegated_uri = (int)v; |
79 | 0 | return 0; |
80 | 0 | } |
81 | | |
82 | | static int on_config_connect(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node) |
83 | 0 | { |
84 | 0 | struct fastcgi_configurator_t *self = (void *)cmd->configurator; |
85 | 0 | const char *hostname = "127.0.0.1", *servname = NULL, *type = "tcp"; |
86 | | |
87 | | /* fetch servname (and hostname) */ |
88 | 0 | switch (node->type) { |
89 | 0 | case YOML_TYPE_SCALAR: |
90 | 0 | servname = node->data.scalar; |
91 | 0 | break; |
92 | 0 | case YOML_TYPE_MAPPING: { |
93 | 0 | yoml_t **port_node, **host_node, **type_node; |
94 | 0 | if (h2o_configurator_parse_mapping(cmd, node, "port:s", "host:s,type:s", &port_node, &host_node, &type_node) != 0) |
95 | 0 | return -1; |
96 | 0 | servname = (*port_node)->data.scalar; |
97 | 0 | if (host_node != NULL) |
98 | 0 | hostname = (*host_node)->data.scalar; |
99 | 0 | if (type_node != NULL) |
100 | 0 | type = (*type_node)->data.scalar; |
101 | 0 | } break; |
102 | 0 | default: |
103 | 0 | h2o_configurator_errprintf(cmd, node, |
104 | 0 | "value must be a string or a mapping (with keys: `port` and optionally `host` and `type`)"); |
105 | 0 | return -1; |
106 | 0 | } |
107 | | |
108 | 0 | h2o_url_t upstream; |
109 | |
|
110 | 0 | if (strcmp(type, "unix") == 0) { |
111 | | /* unix socket */ |
112 | 0 | struct sockaddr_un sa; |
113 | 0 | if (strlen(servname) >= sizeof(sa.sun_path)) { |
114 | 0 | h2o_configurator_errprintf(cmd, node, "path:%s is too long as a unix socket name", servname); |
115 | 0 | return -1; |
116 | 0 | } |
117 | 0 | h2o_url_init_with_sun_path(&upstream, NULL, &H2O_URL_SCHEME_FASTCGI, h2o_iovec_init(servname, strlen(servname)), |
118 | 0 | h2o_iovec_init(H2O_STRLIT("/"))); |
119 | 0 | } else if (strcmp(type, "tcp") == 0) { |
120 | | /* tcp socket */ |
121 | 0 | uint16_t port; |
122 | 0 | if (sscanf(servname, "%" SCNu16, &port) != 1) { |
123 | 0 | h2o_configurator_errprintf(cmd, node, "invalid port number:%s", servname); |
124 | 0 | return -1; |
125 | 0 | } |
126 | 0 | h2o_url_init_with_hostport(&upstream, NULL, &H2O_URL_SCHEME_FASTCGI, h2o_iovec_init(hostname, strlen(hostname)), port, |
127 | 0 | h2o_iovec_init(H2O_STRLIT("/"))); |
128 | 0 | } else { |
129 | 0 | h2o_configurator_errprintf(cmd, node, "unknown listen type: %s", type); |
130 | 0 | return -1; |
131 | 0 | } |
132 | | |
133 | 0 | h2o_fastcgi_register(ctx->pathconf, &upstream, self->vars); |
134 | 0 | free(upstream.authority.base); |
135 | |
|
136 | 0 | return 0; |
137 | 0 | } |
138 | | |
139 | | static int create_spawnproc(h2o_configurator_command_t *cmd, yoml_t *node, const char *dirname, char *const *argv, |
140 | | struct sockaddr_un *sa, struct passwd *pw) |
141 | 0 | { |
142 | 0 | int ret, listen_fd = -1, pipe_fds[2] = {-1, -1}; |
143 | | |
144 | | /* build socket path */ |
145 | 0 | sa->sun_family = AF_UNIX; |
146 | 0 | ret = snprintf(sa->sun_path, sizeof(sa->sun_path), "%s/_", dirname); |
147 | 0 | if (ret < 0 || ret >= sizeof(sa->sun_path)) { |
148 | 0 | h2o_configurator_errprintf(cmd, node, "unix socket path too long: %s", dirname); |
149 | 0 | goto Error; |
150 | 0 | } |
151 | | |
152 | | /* create socket */ |
153 | 0 | if ((listen_fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { |
154 | 0 | h2o_configurator_errprintf(cmd, node, "socket(2) failed: %s", strerror(errno)); |
155 | 0 | goto Error; |
156 | 0 | } |
157 | 0 | if (bind(listen_fd, (void *)sa, sizeof(*sa)) != 0) { |
158 | 0 | h2o_configurator_errprintf(cmd, node, "bind(2) failed: %s", strerror(errno)); |
159 | 0 | goto Error; |
160 | 0 | } |
161 | 0 | if (listen(listen_fd, H2O_SOMAXCONN) != 0) { |
162 | 0 | h2o_configurator_errprintf(cmd, node, "listen(2) failed: %s", strerror(errno)); |
163 | 0 | goto Error; |
164 | 0 | } |
165 | | /* change ownership of socket */ |
166 | 0 | if (pw != NULL && chown(sa->sun_path, pw->pw_uid, pw->pw_gid) != 0) { |
167 | 0 | h2o_configurator_errprintf(cmd, node, "chown(2) failed to change ownership of socket:%s:%s", sa->sun_path, strerror(errno)); |
168 | 0 | goto Error; |
169 | 0 | } |
170 | | |
171 | | /* create pipe which is used to notify the termination of the server */ |
172 | 0 | if (pipe(pipe_fds) != 0) { |
173 | 0 | h2o_configurator_errprintf(cmd, node, "pipe(2) failed: %s", strerror(errno)); |
174 | 0 | pipe_fds[0] = -1; |
175 | 0 | pipe_fds[1] = -1; |
176 | 0 | goto Error; |
177 | 0 | } |
178 | 0 | if (fcntl(pipe_fds[1], F_SETFD, FD_CLOEXEC) < 0) |
179 | 0 | goto Error; |
180 | | |
181 | | /* spawn, mapping listen_fd to fd 0, read-side of the pipe to fd 5 */ |
182 | 0 | int mapped_fds[] = {listen_fd, 0, -1, -1, -1}; |
183 | 0 | if (pipe_fds[0] != 5) { |
184 | 0 | mapped_fds[2] = pipe_fds[0]; |
185 | 0 | mapped_fds[3] = 5; |
186 | 0 | } |
187 | 0 | pid_t pid = h2o_spawnp(argv[0], argv, mapped_fds, 0); |
188 | 0 | if (pid == -1) { |
189 | 0 | h2o_error_printf("[lib/handler/fastcgi.c] failed to launch helper program %s:%s\n", argv[0], strerror(errno)); |
190 | 0 | goto Error; |
191 | 0 | } |
192 | | |
193 | 0 | close(listen_fd); |
194 | 0 | listen_fd = -1; |
195 | 0 | close(pipe_fds[0]); |
196 | 0 | pipe_fds[0] = -1; |
197 | |
|
198 | 0 | return pipe_fds[1]; |
199 | | |
200 | 0 | Error: |
201 | 0 | if (pipe_fds[0] != -1) |
202 | 0 | close(pipe_fds[0]); |
203 | 0 | if (pipe_fds[1]) |
204 | 0 | close(pipe_fds[1]); |
205 | 0 | if (listen_fd != -1) |
206 | 0 | close(listen_fd); |
207 | 0 | unlink(sa->sun_path); |
208 | 0 | return -1; |
209 | 0 | } |
210 | | |
211 | | static void spawnproc_on_dispose(h2o_fastcgi_handler_t *handler, void *data) |
212 | 0 | { |
213 | 0 | int pipe_fd = (int)((char *)data - (char *)NULL); |
214 | 0 | close(pipe_fd); |
215 | 0 | } |
216 | | |
217 | | static int on_config_spawn(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node) |
218 | 0 | { |
219 | 0 | struct fastcgi_configurator_t *self = (void *)cmd->configurator; |
220 | 0 | char *spawn_user = ctx->globalconf->user, *spawn_cmd; |
221 | 0 | char *kill_on_close_cmd_path = NULL, *setuidgid_cmd_path = NULL; |
222 | 0 | char dirname[] = "/tmp/h2o.fcgisock.XXXXXX"; |
223 | 0 | char *argv[10]; |
224 | 0 | int spawner_fd; |
225 | 0 | struct sockaddr_un sa; |
226 | 0 | h2o_fastcgi_config_vars_t config_vars; |
227 | 0 | int ret = -1; |
228 | 0 | struct passwd h2o_user_pwbuf, *h2o_user_pw; |
229 | 0 | char h2o_user_buf[65536]; |
230 | |
|
231 | 0 | memset(&sa, 0, sizeof(sa)); |
232 | |
|
233 | 0 | switch (node->type) { |
234 | 0 | case YOML_TYPE_SCALAR: |
235 | 0 | spawn_cmd = node->data.scalar; |
236 | 0 | break; |
237 | 0 | case YOML_TYPE_MAPPING: { |
238 | 0 | yoml_t **command_node, **user_node; |
239 | 0 | if (h2o_configurator_parse_mapping(cmd, node, "command:s", "user:s", &command_node, &user_node) != 0) |
240 | 0 | return -1; |
241 | 0 | spawn_cmd = (*command_node)->data.scalar; |
242 | 0 | if (user_node != NULL) |
243 | 0 | spawn_user = (*user_node)->data.scalar; |
244 | 0 | } break; |
245 | 0 | default: |
246 | 0 | h2o_configurator_errprintf(cmd, node, "argument must be scalar or mapping"); |
247 | 0 | return -1; |
248 | 0 | } |
249 | | |
250 | | /* obtain uid & gid of the client that connects to the FastCGI daemon (i.e. H2O after dropping privileges) */ |
251 | 0 | if (ctx->globalconf->user != NULL) { |
252 | | /* change ownership of temporary directory */ |
253 | 0 | if (getpwnam_r(ctx->globalconf->user, &h2o_user_pwbuf, h2o_user_buf, sizeof(h2o_user_buf), &h2o_user_pw) != 0 || |
254 | 0 | h2o_user_pw == NULL) { |
255 | 0 | h2o_configurator_errprintf(cmd, node, "getpwnam_r(3) failed to obtain uid of user:%s", ctx->globalconf->user); |
256 | 0 | goto Exit; |
257 | 0 | } |
258 | 0 | } else { |
259 | 0 | h2o_user_pw = NULL; |
260 | 0 | } |
261 | | |
262 | 0 | { /* build args */ |
263 | 0 | size_t i = 0; |
264 | 0 | argv[i++] = kill_on_close_cmd_path = h2o_configurator_get_cmd_path("share/h2o/kill-on-close"); |
265 | 0 | argv[i++] = "--rm"; |
266 | 0 | argv[i++] = dirname; |
267 | 0 | argv[i++] = "--"; |
268 | 0 | if (spawn_user != NULL) { |
269 | 0 | argv[i++] = setuidgid_cmd_path = h2o_configurator_get_cmd_path("share/h2o/setuidgid"); |
270 | 0 | argv[i++] = spawn_user; |
271 | 0 | } |
272 | 0 | argv[i++] = "/bin/sh"; |
273 | 0 | argv[i++] = "-c"; |
274 | 0 | argv[i++] = spawn_cmd; |
275 | 0 | argv[i++] = NULL; |
276 | 0 | assert(i <= sizeof(argv) / sizeof(argv[0])); |
277 | 0 | } |
278 | | |
279 | 0 | if (ctx->dry_run) { |
280 | 0 | dirname[0] = '\0'; |
281 | 0 | spawner_fd = -1; |
282 | 0 | sa.sun_family = AF_UNIX; |
283 | 0 | strcpy(sa.sun_path, "/dry-run.nonexistent"); |
284 | 0 | } else { |
285 | | /* create temporary directory */ |
286 | 0 | if (mkdtemp(dirname) == NULL) { |
287 | 0 | h2o_configurator_errprintf(cmd, node, "mkdtemp(3) failed to create temporary directory:%s:%s", dirname, |
288 | 0 | strerror(errno)); |
289 | 0 | dirname[0] = '\0'; |
290 | 0 | goto Exit; |
291 | 0 | } |
292 | | /* change ownership of temporary directory */ |
293 | 0 | if (h2o_user_pw != NULL && chown(dirname, h2o_user_pw->pw_uid, h2o_user_pw->pw_gid) != 0) { |
294 | 0 | h2o_configurator_errprintf(cmd, node, "chown(2) failed to change ownership of temporary directory:%s:%s", dirname, |
295 | 0 | strerror(errno)); |
296 | 0 | goto Exit; |
297 | 0 | } |
298 | | /* launch spawnfcgi command */ |
299 | 0 | if ((spawner_fd = create_spawnproc(cmd, node, dirname, argv, &sa, h2o_user_pw)) == -1) { |
300 | 0 | goto Exit; |
301 | 0 | } |
302 | 0 | } |
303 | | |
304 | 0 | config_vars = *self->vars; |
305 | 0 | config_vars.callbacks.dispose = spawnproc_on_dispose; |
306 | 0 | config_vars.callbacks.data = (char *)NULL + spawner_fd; |
307 | |
|
308 | 0 | h2o_url_t upstream; |
309 | 0 | h2o_url_init_with_sun_path(&upstream, NULL, &H2O_URL_SCHEME_FASTCGI, h2o_iovec_init(sa.sun_path, strlen(sa.sun_path)), |
310 | 0 | h2o_iovec_init(H2O_STRLIT("/"))); |
311 | 0 | h2o_fastcgi_register(ctx->pathconf, &upstream, &config_vars); |
312 | 0 | free(upstream.authority.base); |
313 | |
|
314 | 0 | ret = 0; |
315 | 0 | Exit: |
316 | 0 | if (dirname[0] != '\0') |
317 | 0 | unlink(dirname); |
318 | 0 | free(kill_on_close_cmd_path); |
319 | 0 | free(setuidgid_cmd_path); |
320 | 0 | return ret; |
321 | 0 | } |
322 | | |
323 | | static int on_config_enter(h2o_configurator_t *_self, h2o_configurator_context_t *ctx, yoml_t *node) |
324 | 0 | { |
325 | 0 | struct fastcgi_configurator_t *self = (void *)_self; |
326 | |
|
327 | 0 | memcpy(self->vars + 1, self->vars, sizeof(*self->vars)); |
328 | 0 | ++self->vars; |
329 | 0 | return 0; |
330 | 0 | } |
331 | | |
332 | | static int on_config_exit(h2o_configurator_t *_self, h2o_configurator_context_t *ctx, yoml_t *node) |
333 | 0 | { |
334 | 0 | struct fastcgi_configurator_t *self = (void *)_self; |
335 | |
|
336 | 0 | --self->vars; |
337 | 0 | return 0; |
338 | 0 | } |
339 | | |
340 | | void h2o_fastcgi_register_configurator(h2o_globalconf_t *conf) |
341 | 0 | { |
342 | 0 | struct fastcgi_configurator_t *c = (void *)h2o_configurator_create(conf, sizeof(*c)); |
343 | | |
344 | | /* set default vars */ |
345 | 0 | c->vars = c->_vars_stack; |
346 | 0 | c->vars->io_timeout = H2O_DEFAULT_FASTCGI_IO_TIMEOUT; |
347 | 0 | c->vars->keepalive_timeout = 0; |
348 | | |
349 | | /* setup handlers */ |
350 | 0 | c->super.enter = on_config_enter; |
351 | 0 | c->super.exit = on_config_exit; |
352 | |
|
353 | 0 | h2o_configurator_define_command(&c->super, "fastcgi.connect", |
354 | 0 | H2O_CONFIGURATOR_FLAG_PATH | H2O_CONFIGURATOR_FLAG_EXTENSION | H2O_CONFIGURATOR_FLAG_DEFERRED, |
355 | 0 | on_config_connect); |
356 | 0 | h2o_configurator_define_command(&c->super, "fastcgi.spawn", |
357 | 0 | H2O_CONFIGURATOR_FLAG_PATH | H2O_CONFIGURATOR_FLAG_EXTENSION | H2O_CONFIGURATOR_FLAG_DEFERRED, |
358 | 0 | on_config_spawn); |
359 | 0 | h2o_configurator_define_command(&c->super, "fastcgi.timeout.io", |
360 | 0 | H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, on_config_timeout_io); |
361 | 0 | h2o_configurator_define_command(&c->super, "fastcgi.timeout.keepalive", |
362 | 0 | H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, |
363 | 0 | on_config_timeout_keepalive); |
364 | 0 | h2o_configurator_define_command(&c->super, "fastcgi.document_root", |
365 | 0 | H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, |
366 | 0 | on_config_document_root); |
367 | 0 | h2o_configurator_define_command(&c->super, "fastcgi.send-delegated-uri", |
368 | 0 | H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, |
369 | 0 | on_config_send_delegated_uri); |
370 | 0 | } |