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
on_config_timeout_io(h2o_configurator_command_t * cmd,h2o_configurator_context_t * ctx,yoml_t * node)42 static int on_config_timeout_io(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
43 {
44 struct fastcgi_configurator_t *self = (void *)cmd->configurator;
45 return h2o_configurator_scanf(cmd, node, "%" SCNu64, &self->vars->io_timeout);
46 }
47
on_config_timeout_keepalive(h2o_configurator_command_t * cmd,h2o_configurator_context_t * ctx,yoml_t * node)48 static int on_config_timeout_keepalive(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
49 {
50 struct fastcgi_configurator_t *self = (void *)cmd->configurator;
51 return h2o_configurator_scanf(cmd, node, "%" SCNu64, &self->vars->keepalive_timeout);
52 }
53
on_config_document_root(h2o_configurator_command_t * cmd,h2o_configurator_context_t * ctx,yoml_t * node)54 static int on_config_document_root(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
55 {
56 struct fastcgi_configurator_t *self = (void *)cmd->configurator;
57
58 if (node->data.scalar[0] == '\0') {
59 /* unset */
60 self->vars->document_root = h2o_iovec_init(NULL, 0);
61 } else if (node->data.scalar[0] == '/') {
62 /* set */
63 self->vars->document_root = h2o_iovec_init(node->data.scalar, strlen(node->data.scalar));
64 } else {
65 h2o_configurator_errprintf(cmd, node, "value does not start from `/`");
66 return -1;
67 }
68 return 0;
69 }
70
on_config_send_delegated_uri(h2o_configurator_command_t * cmd,h2o_configurator_context_t * ctx,yoml_t * node)71 static int on_config_send_delegated_uri(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
72 {
73 struct fastcgi_configurator_t *self = (void *)cmd->configurator;
74 ssize_t v;
75
76 if ((v = h2o_configurator_get_one_of(cmd, node, "OFF,ON")) == -1)
77 return -1;
78 self->vars->send_delegated_uri = (int)v;
79 return 0;
80 }
81
on_config_connect(h2o_configurator_command_t * cmd,h2o_configurator_context_t * ctx,yoml_t * node)82 static int on_config_connect(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
83 {
84 struct fastcgi_configurator_t *self = (void *)cmd->configurator;
85 const char *hostname = "127.0.0.1", *servname = NULL, *type = "tcp";
86
87 /* fetch servname (and hostname) */
88 switch (node->type) {
89 case YOML_TYPE_SCALAR:
90 servname = node->data.scalar;
91 break;
92 case YOML_TYPE_MAPPING: {
93 yoml_t **port_node, **host_node, **type_node;
94 if (h2o_configurator_parse_mapping(cmd, node, "port:s", "host:s,type:s", &port_node, &host_node, &type_node) != 0)
95 return -1;
96 servname = (*port_node)->data.scalar;
97 if (host_node != NULL)
98 hostname = (*host_node)->data.scalar;
99 if (type_node != NULL)
100 type = (*type_node)->data.scalar;
101 } break;
102 default:
103 h2o_configurator_errprintf(cmd, node,
104 "value must be a string or a mapping (with keys: `port` and optionally `host` and `type`)");
105 return -1;
106 }
107
108 h2o_url_t upstream;
109
110 if (strcmp(type, "unix") == 0) {
111 /* unix socket */
112 struct sockaddr_un sa;
113 if (strlen(servname) >= sizeof(sa.sun_path)) {
114 h2o_configurator_errprintf(cmd, node, "path:%s is too long as a unix socket name", servname);
115 return -1;
116 }
117 h2o_url_init_with_sun_path(&upstream, NULL, &H2O_URL_SCHEME_FASTCGI, h2o_iovec_init(servname, strlen(servname)),
118 h2o_iovec_init(H2O_STRLIT("/")));
119 } else if (strcmp(type, "tcp") == 0) {
120 /* tcp socket */
121 uint16_t port;
122 if (sscanf(servname, "%" SCNu16, &port) != 1) {
123 h2o_configurator_errprintf(cmd, node, "invalid port number:%s", servname);
124 return -1;
125 }
126 h2o_url_init_with_hostport(&upstream, NULL, &H2O_URL_SCHEME_FASTCGI, h2o_iovec_init(hostname, strlen(hostname)), port,
127 h2o_iovec_init(H2O_STRLIT("/")));
128 } else {
129 h2o_configurator_errprintf(cmd, node, "unknown listen type: %s", type);
130 return -1;
131 }
132
133 h2o_fastcgi_register(ctx->pathconf, &upstream, self->vars);
134 free(upstream.authority.base);
135
136 return 0;
137 }
138
create_spawnproc(h2o_configurator_command_t * cmd,yoml_t * node,const char * dirname,char * const * argv,struct sockaddr_un * sa,struct passwd * pw)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 {
142 int ret, listen_fd = -1, pipe_fds[2] = {-1, -1};
143
144 /* build socket path */
145 sa->sun_family = AF_UNIX;
146 ret = snprintf(sa->sun_path, sizeof(sa->sun_path), "%s/_", dirname);
147 if (ret < 0 || ret >= sizeof(sa->sun_path)) {
148 h2o_configurator_errprintf(cmd, node, "unix socket path too long: %s", dirname);
149 goto Error;
150 }
151
152 /* create socket */
153 if ((listen_fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
154 h2o_configurator_errprintf(cmd, node, "socket(2) failed: %s", strerror(errno));
155 goto Error;
156 }
157 if (bind(listen_fd, (void *)sa, sizeof(*sa)) != 0) {
158 h2o_configurator_errprintf(cmd, node, "bind(2) failed: %s", strerror(errno));
159 goto Error;
160 }
161 if (listen(listen_fd, H2O_SOMAXCONN) != 0) {
162 h2o_configurator_errprintf(cmd, node, "listen(2) failed: %s", strerror(errno));
163 goto Error;
164 }
165 /* change ownership of socket */
166 if (pw != NULL && chown(sa->sun_path, pw->pw_uid, pw->pw_gid) != 0) {
167 h2o_configurator_errprintf(cmd, node, "chown(2) failed to change ownership of socket:%s:%s", sa->sun_path, strerror(errno));
168 goto Error;
169 }
170
171 /* create pipe which is used to notify the termination of the server */
172 if (pipe(pipe_fds) != 0) {
173 h2o_configurator_errprintf(cmd, node, "pipe(2) failed: %s", strerror(errno));
174 pipe_fds[0] = -1;
175 pipe_fds[1] = -1;
176 goto Error;
177 }
178 if (fcntl(pipe_fds[1], F_SETFD, FD_CLOEXEC) < 0)
179 goto Error;
180
181 /* spawn */
182 int mapped_fds[] = {listen_fd, 0, /* listen_fd to 0 */
183 pipe_fds[0], 5, /* pipe_fds[0] to 5 */
184 -1};
185 pid_t pid = h2o_spawnp(argv[0], argv, mapped_fds, 0);
186 if (pid == -1) {
187 h2o_error_printf("[lib/handler/fastcgi.c] failed to launch helper program %s:%s\n", argv[0], strerror(errno));
188 goto Error;
189 }
190
191 close(listen_fd);
192 listen_fd = -1;
193 close(pipe_fds[0]);
194 pipe_fds[0] = -1;
195
196 return pipe_fds[1];
197
198 Error:
199 if (pipe_fds[0] != -1)
200 close(pipe_fds[0]);
201 if (pipe_fds[1])
202 close(pipe_fds[1]);
203 if (listen_fd != -1)
204 close(listen_fd);
205 unlink(sa->sun_path);
206 return -1;
207 }
208
spawnproc_on_dispose(h2o_fastcgi_handler_t * handler,void * data)209 static void spawnproc_on_dispose(h2o_fastcgi_handler_t *handler, void *data)
210 {
211 int pipe_fd = (int)((char *)data - (char *)NULL);
212 close(pipe_fd);
213 }
214
on_config_spawn(h2o_configurator_command_t * cmd,h2o_configurator_context_t * ctx,yoml_t * node)215 static int on_config_spawn(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
216 {
217 struct fastcgi_configurator_t *self = (void *)cmd->configurator;
218 char *spawn_user = ctx->globalconf->user, *spawn_cmd;
219 char *kill_on_close_cmd_path = NULL, *setuidgid_cmd_path = NULL;
220 char dirname[] = "/tmp/h2o.fcgisock.XXXXXX";
221 char *argv[10];
222 int spawner_fd;
223 struct sockaddr_un sa;
224 h2o_fastcgi_config_vars_t config_vars;
225 int ret = -1;
226 struct passwd h2o_user_pwbuf, *h2o_user_pw;
227 char h2o_user_buf[65536];
228
229 memset(&sa, 0, sizeof(sa));
230
231 switch (node->type) {
232 case YOML_TYPE_SCALAR:
233 spawn_cmd = node->data.scalar;
234 break;
235 case YOML_TYPE_MAPPING: {
236 yoml_t **command_node, **user_node;
237 if (h2o_configurator_parse_mapping(cmd, node, "command:s", "user:s", &command_node, &user_node) != 0)
238 return -1;
239 spawn_cmd = (*command_node)->data.scalar;
240 if (user_node != NULL)
241 spawn_user = (*user_node)->data.scalar;
242 } break;
243 default:
244 h2o_configurator_errprintf(cmd, node, "argument must be scalar or mapping");
245 return -1;
246 }
247
248 /* obtain uid & gid of the client that connects to the FastCGI daemon (i.e. H2O after dropping privileges) */
249 if (ctx->globalconf->user != NULL) {
250 /* change ownership of temporary directory */
251 if (getpwnam_r(ctx->globalconf->user, &h2o_user_pwbuf, h2o_user_buf, sizeof(h2o_user_buf), &h2o_user_pw) != 0 ||
252 h2o_user_pw == NULL) {
253 h2o_configurator_errprintf(cmd, node, "getpwnam_r(3) failed to obtain uid of user:%s", ctx->globalconf->user);
254 goto Exit;
255 }
256 } else {
257 h2o_user_pw = NULL;
258 }
259
260 { /* build args */
261 size_t i = 0;
262 argv[i++] = kill_on_close_cmd_path = h2o_configurator_get_cmd_path("share/h2o/kill-on-close");
263 argv[i++] = "--rm";
264 argv[i++] = dirname;
265 argv[i++] = "--";
266 if (spawn_user != NULL) {
267 argv[i++] = setuidgid_cmd_path = h2o_configurator_get_cmd_path("share/h2o/setuidgid");
268 argv[i++] = spawn_user;
269 }
270 argv[i++] = "/bin/sh";
271 argv[i++] = "-c";
272 argv[i++] = spawn_cmd;
273 argv[i++] = NULL;
274 assert(i <= sizeof(argv) / sizeof(argv[0]));
275 }
276
277 if (ctx->dry_run) {
278 dirname[0] = '\0';
279 spawner_fd = -1;
280 sa.sun_family = AF_UNIX;
281 strcpy(sa.sun_path, "/dry-run.nonexistent");
282 } else {
283 /* create temporary directory */
284 if (mkdtemp(dirname) == NULL) {
285 h2o_configurator_errprintf(cmd, node, "mkdtemp(3) failed to create temporary directory:%s:%s", dirname,
286 strerror(errno));
287 dirname[0] = '\0';
288 goto Exit;
289 }
290 /* change ownership of temporary directory */
291 if (h2o_user_pw != NULL && chown(dirname, h2o_user_pw->pw_uid, h2o_user_pw->pw_gid) != 0) {
292 h2o_configurator_errprintf(cmd, node, "chown(2) failed to change ownership of temporary directory:%s:%s", dirname,
293 strerror(errno));
294 goto Exit;
295 }
296 /* launch spawnfcgi command */
297 if ((spawner_fd = create_spawnproc(cmd, node, dirname, argv, &sa, h2o_user_pw)) == -1) {
298 goto Exit;
299 }
300 }
301
302 config_vars = *self->vars;
303 config_vars.callbacks.dispose = spawnproc_on_dispose;
304 config_vars.callbacks.data = (char *)NULL + spawner_fd;
305
306 h2o_url_t upstream;
307 h2o_url_init_with_sun_path(&upstream, NULL, &H2O_URL_SCHEME_FASTCGI, h2o_iovec_init(sa.sun_path, strlen(sa.sun_path)),
308 h2o_iovec_init(H2O_STRLIT("/")));
309 h2o_fastcgi_register(ctx->pathconf, &upstream, &config_vars);
310 free(upstream.authority.base);
311
312 ret = 0;
313 Exit:
314 if (dirname[0] != '\0')
315 unlink(dirname);
316 free(kill_on_close_cmd_path);
317 free(setuidgid_cmd_path);
318 return ret;
319 }
320
on_config_enter(h2o_configurator_t * _self,h2o_configurator_context_t * ctx,yoml_t * node)321 static int on_config_enter(h2o_configurator_t *_self, h2o_configurator_context_t *ctx, yoml_t *node)
322 {
323 struct fastcgi_configurator_t *self = (void *)_self;
324
325 memcpy(self->vars + 1, self->vars, sizeof(*self->vars));
326 ++self->vars;
327 return 0;
328 }
329
on_config_exit(h2o_configurator_t * _self,h2o_configurator_context_t * ctx,yoml_t * node)330 static int on_config_exit(h2o_configurator_t *_self, h2o_configurator_context_t *ctx, yoml_t *node)
331 {
332 struct fastcgi_configurator_t *self = (void *)_self;
333
334 --self->vars;
335 return 0;
336 }
337
h2o_fastcgi_register_configurator(h2o_globalconf_t * conf)338 void h2o_fastcgi_register_configurator(h2o_globalconf_t *conf)
339 {
340 struct fastcgi_configurator_t *c = (void *)h2o_configurator_create(conf, sizeof(*c));
341
342 /* set default vars */
343 c->vars = c->_vars_stack;
344 c->vars->io_timeout = H2O_DEFAULT_FASTCGI_IO_TIMEOUT;
345 c->vars->keepalive_timeout = 0;
346
347 /* setup handlers */
348 c->super.enter = on_config_enter;
349 c->super.exit = on_config_exit;
350
351 h2o_configurator_define_command(&c->super, "fastcgi.connect",
352 H2O_CONFIGURATOR_FLAG_PATH | H2O_CONFIGURATOR_FLAG_EXTENSION | H2O_CONFIGURATOR_FLAG_DEFERRED,
353 on_config_connect);
354 h2o_configurator_define_command(&c->super, "fastcgi.spawn",
355 H2O_CONFIGURATOR_FLAG_PATH | H2O_CONFIGURATOR_FLAG_EXTENSION | H2O_CONFIGURATOR_FLAG_DEFERRED,
356 on_config_spawn);
357 h2o_configurator_define_command(&c->super, "fastcgi.timeout.io",
358 H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, on_config_timeout_io);
359 h2o_configurator_define_command(&c->super, "fastcgi.timeout.keepalive",
360 H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
361 on_config_timeout_keepalive);
362 h2o_configurator_define_command(&c->super, "fastcgi.document_root",
363 H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
364 on_config_document_root);
365 h2o_configurator_define_command(&c->super, "fastcgi.send-delegated-uri",
366 H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
367 on_config_send_delegated_uri);
368 }
369