Coverage Report

Created: 2025-06-22 06:17

/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
}