├── .gitignore ├── LICENSE.md ├── Makefile ├── README.md ├── TODO.md ├── httpd ├── Makefile ├── config.c ├── control.c ├── http.h ├── httpd.8 ├── httpd.c ├── httpd.conf.5 ├── httpd.h ├── log.c ├── logger.c ├── parse.y ├── patterns.7 ├── patterns.c ├── patterns.h ├── proc.c ├── server.c ├── server_fcgi.c ├── server_file.c └── server_http.c ├── regress ├── Makefile ├── patterns │ ├── Makefile │ ├── patterns-tester.c │ ├── patterns-tester.lua │ ├── test-patterns-lua.out │ ├── test-patterns.in │ └── test-patterns.out └── tests │ ├── Client.pm │ ├── Httpd.pm │ ├── LICENSE │ ├── Makefile │ ├── Proc.pm │ ├── README │ ├── args-default.pl │ ├── args-get-1048576.pl │ ├── args-get-1073741824.pl │ ├── args-get-512.pl │ ├── args-get-slash.pl │ ├── args-log-user-agent.pl │ ├── args-tls-get-1073741824.pl │ ├── args-tls.pl │ ├── funcs.pl │ └── httpd.pl └── rfc ├── rfc3875.txt ├── rfc7230.txt ├── rfc7231.txt ├── rfc7232.txt ├── rfc7233.txt ├── rfc7234.txt └── rfc7235.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .cvsignore 2 | CVS 3 | obj 4 | *.manlint 5 | *~ 6 | *.core 7 | *.o 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | License 2 | ======= 3 | 4 | httpd is free software under OpenBSD's ISC-style license. 5 | 6 | * httpd is based on OpenBSD relayd 7 | * Most of the code has been written by Reyk Floeter 8 | * And initially also by Pierre-Yves Ritschard 9 | * FastCGI code has been written by Florian Obser 10 | * Please refer to the individual source files for other copyright holders! 11 | * Files in rfc/ are provided as reference; see their "Copyright Notice" 12 | 13 | > Copyright (c) 2007-2015 Reyk Floeter 14 | > 15 | > Permission to use, copy, modify, and distribute this software for any 16 | > purpose with or without fee is hereby granted, provided that the above 17 | > copyright notice and this permission notice appear in all copies. 18 | > 19 | > THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 20 | > WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 21 | > MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 22 | > ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 23 | > WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 24 | > ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 25 | > OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 26 | 27 | * One exception is 28 | [`patterns.c`](https://github.com/reyk/httpd/blob/master/httpd/patterns.c) 29 | that is derived from the pattern matching code in Lua's `lstrlib.c`. 30 | 31 | > Copyright (c) 2015 Reyk Floeter 32 | > Copyright (C) 1994-2015 Lua.org, PUC-Rio. 33 | > 34 | > Permission is hereby granted, free of charge, to any person obtaining 35 | > a copy of this software and associated documentation files (the 36 | > "Software"), to deal in the Software without restriction, including 37 | > without limitation the rights to use, copy, modify, merge, publish, 38 | > distribute, sublicense, and/or sell copies of the Software, and to 39 | > permit persons to whom the Software is furnished to do so, subject to 40 | > the following conditions: 41 | > 42 | > The above copyright notice and this permission notice shall be 43 | > included in all copies or substantial portions of the Software. 44 | > 45 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 46 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 47 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 48 | > IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 49 | > CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 50 | > TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 51 | > SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 52 | > 53 | > Derived from Lua 5.3.1: 54 | > $Id: patterns.c,v 1.2 2015/06/23 15:35:20 semarie Exp $ 55 | > Standard library for string operations and pattern-matching 56 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SUBDIR= httpd 2 | MAKE_FLAGS= BINDIR=/usr/sbin SUDO=sudo 3 | 4 | .include 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | OpenBSD httpd 2 | ============= 3 | 4 | httpd(8) is OpenBSD's web server. 5 | 6 | * http://bsd.plumbing/ 7 | 8 | * http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.sbin/httpd/ 9 | 10 | See [`LICENSE.md`](https://github.com/reyk/httpd/blob/master/LICENSE.md) 11 | for information about copyright and licensing. 12 | 13 | See [`TODO.md`](https://github.com/reyk/httpd/blob/master/TODO.md) for 14 | a (possibly outdated) list of open issues. 15 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | OpenBSD httpd TODO 2 | ================== 3 | 4 | Have a look at https://github.com/reyk/httpd/issues. 5 | -------------------------------------------------------------------------------- /httpd/Makefile: -------------------------------------------------------------------------------- 1 | # $OpenBSD: Makefile,v 1.30 2017/07/03 22:21:47 espie Exp $ 2 | 3 | PROG= httpd 4 | SRCS= parse.y 5 | SRCS+= config.c control.c httpd.c log.c logger.c proc.c 6 | SRCS+= server.c server_http.c server_file.c server_fcgi.c 7 | MAN= httpd.8 httpd.conf.5 8 | 9 | SRCS+= patterns.c 10 | MAN+= patterns.7 11 | 12 | LDADD= -levent -ltls -lssl -lcrypto -lutil 13 | DPADD= ${LIBEVENT} ${LIBTLS} ${LIBSSL} ${LIBCRYPTO} ${LIBUTIL} 14 | #DEBUG= -g -DDEBUG=3 -O0 15 | CFLAGS+= -Wall -I${.CURDIR} 16 | CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes 17 | CFLAGS+= -Wmissing-declarations 18 | CFLAGS+= -Wshadow -Wpointer-arith 19 | CFLAGS+= -Wsign-compare -Wcast-qual 20 | YFLAGS= 21 | 22 | .include 23 | -------------------------------------------------------------------------------- /httpd/config.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: config.c,v 1.53 2017/07/19 17:36:25 jsing Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 2011 - 2015 Reyk Floeter 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "httpd.h" 32 | 33 | int config_getserver_config(struct httpd *, struct server *, 34 | struct imsg *); 35 | int config_getserver_auth(struct httpd *, struct server_config *); 36 | 37 | int 38 | config_init(struct httpd *env) 39 | { 40 | struct privsep *ps = env->sc_ps; 41 | unsigned int what; 42 | 43 | /* Global configuration */ 44 | if (privsep_process == PROC_PARENT) 45 | env->sc_prefork_server = SERVER_NUMPROC; 46 | 47 | ps->ps_what[PROC_PARENT] = CONFIG_ALL; 48 | ps->ps_what[PROC_SERVER] = 49 | CONFIG_SERVERS|CONFIG_MEDIA|CONFIG_AUTH; 50 | ps->ps_what[PROC_LOGGER] = CONFIG_SERVERS; 51 | 52 | /* Other configuration */ 53 | what = ps->ps_what[privsep_process]; 54 | 55 | if (what & CONFIG_SERVERS) { 56 | if ((env->sc_servers = 57 | calloc(1, sizeof(*env->sc_servers))) == NULL) 58 | return (-1); 59 | TAILQ_INIT(env->sc_servers); 60 | } 61 | 62 | if (what & CONFIG_MEDIA) { 63 | if ((env->sc_mediatypes = 64 | calloc(1, sizeof(*env->sc_mediatypes))) == NULL) 65 | return (-1); 66 | RB_INIT(env->sc_mediatypes); 67 | } 68 | 69 | if (what & CONFIG_AUTH) { 70 | if ((env->sc_auth = 71 | calloc(1, sizeof(*env->sc_auth))) == NULL) 72 | return (-1); 73 | TAILQ_INIT(env->sc_auth); 74 | } 75 | 76 | return (0); 77 | } 78 | 79 | void 80 | config_purge(struct httpd *env, unsigned int reset) 81 | { 82 | struct privsep *ps = env->sc_ps; 83 | struct server *srv; 84 | struct auth *auth; 85 | unsigned int what; 86 | 87 | what = ps->ps_what[privsep_process] & reset; 88 | 89 | if (what & CONFIG_SERVERS && env->sc_servers != NULL) { 90 | while ((srv = TAILQ_FIRST(env->sc_servers)) != NULL) 91 | server_purge(srv); 92 | } 93 | 94 | if (what & CONFIG_MEDIA && env->sc_mediatypes != NULL) 95 | media_purge(env->sc_mediatypes); 96 | 97 | if (what & CONFIG_AUTH && env->sc_auth != NULL) { 98 | while ((auth = TAILQ_FIRST(env->sc_auth)) != NULL) { 99 | auth_free(env->sc_auth, auth); 100 | free(auth); 101 | } 102 | } 103 | } 104 | 105 | int 106 | config_setreset(struct httpd *env, unsigned int reset) 107 | { 108 | struct privsep *ps = env->sc_ps; 109 | int id; 110 | 111 | for (id = 0; id < PROC_MAX; id++) { 112 | if ((reset & ps->ps_what[id]) == 0 || 113 | id == privsep_process) 114 | continue; 115 | proc_compose(ps, id, IMSG_CTL_RESET, 116 | &reset, sizeof(reset)); 117 | } 118 | 119 | return (0); 120 | } 121 | 122 | int 123 | config_getreset(struct httpd *env, struct imsg *imsg) 124 | { 125 | unsigned int mode; 126 | 127 | IMSG_SIZE_CHECK(imsg, &mode); 128 | memcpy(&mode, imsg->data, sizeof(mode)); 129 | 130 | config_purge(env, mode); 131 | 132 | return (0); 133 | } 134 | 135 | int 136 | config_getcfg(struct httpd *env, struct imsg *imsg) 137 | { 138 | struct privsep *ps = env->sc_ps; 139 | struct ctl_flags cf; 140 | unsigned int what; 141 | 142 | if (IMSG_DATA_SIZE(imsg) != sizeof(cf)) 143 | return (0); /* ignore */ 144 | 145 | /* Update runtime flags */ 146 | memcpy(&cf, imsg->data, sizeof(cf)); 147 | env->sc_opts = cf.cf_opts; 148 | env->sc_flags = cf.cf_flags; 149 | memcpy(env->sc_tls_sid, cf.cf_tls_sid, sizeof(env->sc_tls_sid)); 150 | 151 | what = ps->ps_what[privsep_process]; 152 | 153 | if (privsep_process != PROC_PARENT) 154 | proc_compose(env->sc_ps, PROC_PARENT, 155 | IMSG_CFG_DONE, NULL, 0); 156 | 157 | return (0); 158 | } 159 | 160 | int 161 | config_setserver(struct httpd *env, struct server *srv) 162 | { 163 | struct privsep *ps = env->sc_ps; 164 | struct server_config s; 165 | int id; 166 | int fd, n, m; 167 | struct iovec iov[6]; 168 | size_t c; 169 | unsigned int what; 170 | 171 | /* opens listening sockets etc. */ 172 | if (server_privinit(srv) == -1) 173 | return (-1); 174 | 175 | for (id = 0; id < PROC_MAX; id++) { 176 | what = ps->ps_what[id]; 177 | 178 | if ((what & CONFIG_SERVERS) == 0 || id == privsep_process) 179 | continue; 180 | 181 | DPRINTF("%s: sending %s \"%s[%u]\" to %s fd %d", __func__, 182 | (srv->srv_conf.flags & SRVFLAG_LOCATION) ? 183 | "location" : "server", 184 | srv->srv_conf.name, srv->srv_conf.id, 185 | ps->ps_title[id], srv->srv_s); 186 | 187 | memcpy(&s, &srv->srv_conf, sizeof(s)); 188 | 189 | c = 0; 190 | iov[c].iov_base = &s; 191 | iov[c++].iov_len = sizeof(s); 192 | if (srv->srv_conf.return_uri_len != 0) { 193 | iov[c].iov_base = srv->srv_conf.return_uri; 194 | iov[c++].iov_len = srv->srv_conf.return_uri_len; 195 | } 196 | 197 | if (id == PROC_SERVER && 198 | (srv->srv_conf.flags & SRVFLAG_LOCATION) == 0) { 199 | /* XXX imsg code will close the fd after 1st call */ 200 | n = -1; 201 | proc_range(ps, id, &n, &m); 202 | for (n = 0; n < m; n++) { 203 | if (srv->srv_s == -1) 204 | fd = -1; 205 | else if ((fd = dup(srv->srv_s)) == -1) 206 | return (-1); 207 | if (proc_composev_imsg(ps, id, n, 208 | IMSG_CFG_SERVER, -1, fd, iov, c) != 0) { 209 | log_warn("%s: failed to compose " 210 | "IMSG_CFG_SERVER imsg for `%s'", 211 | __func__, srv->srv_conf.name); 212 | return (-1); 213 | } 214 | 215 | /* Prevent fd exhaustion in the parent. */ 216 | if (proc_flush_imsg(ps, id, n) == -1) { 217 | log_warn("%s: failed to flush " 218 | "IMSG_CFG_SERVER imsg for `%s'", 219 | __func__, srv->srv_conf.name); 220 | return (-1); 221 | } 222 | } 223 | 224 | /* Configure TLS if necessary. */ 225 | config_setserver_tls(env, srv); 226 | } else { 227 | if (proc_composev(ps, id, IMSG_CFG_SERVER, 228 | iov, c) != 0) { 229 | log_warn("%s: failed to compose " 230 | "IMSG_CFG_SERVER imsg for `%s'", 231 | __func__, srv->srv_conf.name); 232 | return (-1); 233 | } 234 | } 235 | } 236 | 237 | /* Close server socket early to prevent fd exhaustion in the parent. */ 238 | if (srv->srv_s != -1) { 239 | close(srv->srv_s); 240 | srv->srv_s = -1; 241 | } 242 | 243 | explicit_bzero(&srv->srv_conf.tls_ticket_key, 244 | sizeof(srv->srv_conf.tls_ticket_key)); 245 | 246 | return (0); 247 | } 248 | 249 | static int 250 | config_settls(struct httpd *env, struct server *srv, enum tls_config_type type, 251 | const char *label, uint8_t *data, size_t len) 252 | { 253 | struct privsep *ps = env->sc_ps; 254 | struct server_config *srv_conf = &srv->srv_conf; 255 | struct tls_config tls; 256 | struct iovec iov[2]; 257 | size_t c; 258 | 259 | if (data == NULL || len == 0) 260 | return (0); 261 | 262 | DPRINTF("%s: sending tls %s for \"%s[%u]\" to %s fd %d", __func__, 263 | label, srv_conf->name, srv_conf->id, ps->ps_title[PROC_SERVER], 264 | srv->srv_s); 265 | 266 | memset(&tls, 0, sizeof(tls)); 267 | tls.id = srv_conf->id; 268 | tls.tls_type = type; 269 | tls.tls_len = len; 270 | tls.tls_chunk_offset = 0; 271 | 272 | while (len > 0) { 273 | tls.tls_chunk_len = len; 274 | if (tls.tls_chunk_len > (MAX_IMSG_DATA_SIZE - sizeof(tls))) 275 | tls.tls_chunk_len = MAX_IMSG_DATA_SIZE - sizeof(tls); 276 | 277 | c = 0; 278 | iov[c].iov_base = &tls; 279 | iov[c++].iov_len = sizeof(tls); 280 | iov[c].iov_base = data; 281 | iov[c++].iov_len = tls.tls_chunk_len; 282 | 283 | if (proc_composev(ps, PROC_SERVER, IMSG_CFG_TLS, iov, c) != 0) { 284 | log_warn("%s: failed to compose IMSG_CFG_TLS imsg for " 285 | "`%s'", __func__, srv_conf->name); 286 | return (-1); 287 | } 288 | 289 | tls.tls_chunk_offset += tls.tls_chunk_len; 290 | data += tls.tls_chunk_len; 291 | len -= tls.tls_chunk_len; 292 | } 293 | 294 | return (0); 295 | } 296 | 297 | int 298 | config_setserver_tls(struct httpd *env, struct server *srv) 299 | { 300 | struct server_config *srv_conf = &srv->srv_conf; 301 | 302 | if ((srv_conf->flags & SRVFLAG_TLS) == 0) 303 | return (0); 304 | 305 | log_debug("%s: configuring tls for %s", __func__, srv_conf->name); 306 | 307 | if (config_settls(env, srv, TLS_CFG_CERT, "cert", srv_conf->tls_cert, 308 | srv_conf->tls_cert_len) != 0) 309 | return (-1); 310 | 311 | if (config_settls(env, srv, TLS_CFG_KEY, "key", srv_conf->tls_key, 312 | srv_conf->tls_key_len) != 0) 313 | return (-1); 314 | 315 | if (config_settls(env, srv, TLS_CFG_OCSP_STAPLE, "ocsp staple", 316 | srv_conf->tls_ocsp_staple, srv_conf->tls_ocsp_staple_len) != 0) 317 | return (-1); 318 | 319 | return (0); 320 | } 321 | 322 | int 323 | config_getserver_auth(struct httpd *env, struct server_config *srv_conf) 324 | { 325 | struct privsep *ps = env->sc_ps; 326 | 327 | if ((ps->ps_what[privsep_process] & CONFIG_AUTH) == 0 || 328 | (srv_conf->flags & SRVFLAG_AUTH) == 0) 329 | return (0); 330 | 331 | if ((srv_conf->auth = auth_byid(env->sc_auth, 332 | srv_conf->auth_id)) == NULL) 333 | return (-1); 334 | 335 | return (0); 336 | } 337 | 338 | int 339 | config_getserver_config(struct httpd *env, struct server *srv, 340 | struct imsg *imsg) 341 | { 342 | #ifdef DEBUG 343 | struct privsep *ps = env->sc_ps; 344 | #endif 345 | struct server_config *srv_conf, *parent; 346 | uint8_t *p = imsg->data; 347 | unsigned int f; 348 | size_t s; 349 | 350 | if ((srv_conf = calloc(1, sizeof(*srv_conf))) == NULL) 351 | return (-1); 352 | 353 | IMSG_SIZE_CHECK(imsg, srv_conf); 354 | memcpy(srv_conf, p, sizeof(*srv_conf)); 355 | s = sizeof(*srv_conf); 356 | 357 | /* Reset these variables to avoid free'ing invalid pointers */ 358 | serverconfig_reset(srv_conf); 359 | 360 | TAILQ_FOREACH(parent, &srv->srv_hosts, entry) { 361 | if (strcmp(parent->name, srv_conf->name) == 0) 362 | break; 363 | } 364 | if (parent == NULL) 365 | parent = &srv->srv_conf; 366 | 367 | if (config_getserver_auth(env, srv_conf) != 0) 368 | goto fail; 369 | 370 | /* 371 | * Get variable-length values for the virtual host. The tls_* ones 372 | * aren't needed in the virtual hosts unless we implement SNI. 373 | */ 374 | if (srv_conf->return_uri_len != 0) { 375 | if ((srv_conf->return_uri = get_data(p + s, 376 | srv_conf->return_uri_len)) == NULL) 377 | goto fail; 378 | s += srv_conf->return_uri_len; 379 | } 380 | 381 | if (srv_conf->flags & SRVFLAG_LOCATION) { 382 | /* Inherit configuration from the parent */ 383 | f = SRVFLAG_INDEX|SRVFLAG_NO_INDEX; 384 | if ((srv_conf->flags & f) == 0) { 385 | srv_conf->flags |= parent->flags & f; 386 | (void)strlcpy(srv_conf->index, parent->index, 387 | sizeof(srv_conf->index)); 388 | } 389 | 390 | f = SRVFLAG_AUTO_INDEX|SRVFLAG_NO_AUTO_INDEX; 391 | if ((srv_conf->flags & f) == 0) 392 | srv_conf->flags |= parent->flags & f; 393 | 394 | f = SRVFLAG_SOCKET|SRVFLAG_FCGI; 395 | if ((srv_conf->flags & f) == SRVFLAG_FCGI) { 396 | srv_conf->flags |= f; 397 | (void)strlcpy(srv_conf->socket, HTTPD_FCGI_SOCKET, 398 | sizeof(srv_conf->socket)); 399 | } 400 | 401 | f = SRVFLAG_ROOT; 402 | if ((srv_conf->flags & f) == 0) { 403 | srv_conf->flags |= parent->flags & f; 404 | (void)strlcpy(srv_conf->root, parent->root, 405 | sizeof(srv_conf->root)); 406 | } 407 | 408 | f = SRVFLAG_FCGI|SRVFLAG_NO_FCGI; 409 | if ((srv_conf->flags & f) == 0) 410 | srv_conf->flags |= parent->flags & f; 411 | 412 | f = SRVFLAG_LOG|SRVFLAG_NO_LOG; 413 | if ((srv_conf->flags & f) == 0) { 414 | srv_conf->flags |= parent->flags & f; 415 | srv_conf->logformat = parent->logformat; 416 | } 417 | 418 | f = SRVFLAG_SYSLOG|SRVFLAG_NO_SYSLOG; 419 | if ((srv_conf->flags & f) == 0) 420 | srv_conf->flags |= parent->flags & f; 421 | 422 | f = SRVFLAG_AUTH|SRVFLAG_NO_AUTH; 423 | if ((srv_conf->flags & f) == 0) { 424 | srv_conf->flags |= parent->flags & f; 425 | srv_conf->auth = parent->auth; 426 | srv_conf->auth_id = parent->auth_id; 427 | (void)strlcpy(srv_conf->auth_realm, 428 | parent->auth_realm, 429 | sizeof(srv_conf->auth_realm)); 430 | } 431 | 432 | f = SRVFLAG_TLS; 433 | srv_conf->flags |= parent->flags & f; 434 | 435 | f = SRVFLAG_ACCESS_LOG; 436 | if ((srv_conf->flags & f) == 0) { 437 | srv_conf->flags |= parent->flags & f; 438 | (void)strlcpy(srv_conf->accesslog, 439 | parent->accesslog, 440 | sizeof(srv_conf->accesslog)); 441 | } 442 | 443 | f = SRVFLAG_ERROR_LOG; 444 | if ((srv_conf->flags & f) == 0) { 445 | srv_conf->flags |= parent->flags & f; 446 | (void)strlcpy(srv_conf->errorlog, 447 | parent->errorlog, 448 | sizeof(srv_conf->errorlog)); 449 | } 450 | 451 | f = SRVFLAG_BLOCK|SRVFLAG_NO_BLOCK; 452 | if ((srv_conf->flags & f) == 0) { 453 | free(srv_conf->return_uri); 454 | srv_conf->flags |= parent->flags & f; 455 | srv_conf->return_code = parent->return_code; 456 | srv_conf->return_uri_len = parent->return_uri_len; 457 | if (srv_conf->return_uri_len && 458 | (srv_conf->return_uri = 459 | strdup(parent->return_uri)) == NULL) 460 | goto fail; 461 | } 462 | 463 | f = SRVFLAG_DEFAULT_TYPE; 464 | if ((srv_conf->flags & f) == 0) { 465 | srv_conf->flags |= parent->flags & f; 466 | memcpy(&srv_conf->default_type, 467 | &parent->default_type, sizeof(struct media_type)); 468 | } 469 | 470 | f = SRVFLAG_SERVER_HSTS; 471 | srv_conf->flags |= parent->flags & f; 472 | srv_conf->hsts_max_age = parent->hsts_max_age; 473 | srv_conf->hsts_flags = parent->hsts_flags; 474 | 475 | memcpy(&srv_conf->timeout, &parent->timeout, 476 | sizeof(srv_conf->timeout)); 477 | srv_conf->maxrequests = parent->maxrequests; 478 | srv_conf->maxrequestbody = parent->maxrequestbody; 479 | 480 | DPRINTF("%s: %s %d location \"%s\", " 481 | "parent \"%s[%u]\", flags: %s", 482 | __func__, ps->ps_title[privsep_process], ps->ps_instance, 483 | srv_conf->location, parent->name, parent->id, 484 | printb_flags(srv_conf->flags, SRVFLAG_BITS)); 485 | } else { 486 | /* Add a new "virtual" server */ 487 | DPRINTF("%s: %s %d server \"%s[%u]\", parent \"%s[%u]\", " 488 | "flags: %s", __func__, 489 | ps->ps_title[privsep_process], ps->ps_instance, 490 | srv_conf->name, srv_conf->id, parent->name, parent->id, 491 | printb_flags(srv_conf->flags, SRVFLAG_BITS)); 492 | } 493 | 494 | TAILQ_INSERT_TAIL(&srv->srv_hosts, srv_conf, entry); 495 | 496 | return (0); 497 | 498 | fail: 499 | serverconfig_free(srv_conf); 500 | free(srv_conf); 501 | return (-1); 502 | } 503 | 504 | int 505 | config_getserver(struct httpd *env, struct imsg *imsg) 506 | { 507 | #ifdef DEBUG 508 | struct privsep *ps = env->sc_ps; 509 | #endif 510 | struct server *srv = NULL; 511 | struct server_config srv_conf; 512 | uint8_t *p = imsg->data; 513 | size_t s; 514 | 515 | IMSG_SIZE_CHECK(imsg, &srv_conf); 516 | memcpy(&srv_conf, p, sizeof(srv_conf)); 517 | s = sizeof(srv_conf); 518 | 519 | /* Reset these variables to avoid free'ing invalid pointers */ 520 | serverconfig_reset(&srv_conf); 521 | 522 | if ((IMSG_DATA_SIZE(imsg) - s) < (size_t)srv_conf.return_uri_len) { 523 | log_debug("%s: invalid message length", __func__); 524 | goto fail; 525 | } 526 | 527 | /* Check if server with matching listening socket already exists */ 528 | if ((srv = server_byaddr((struct sockaddr *) 529 | &srv_conf.ss, srv_conf.port)) != NULL) { 530 | /* Add "host" to existing listening server */ 531 | if (imsg->fd != -1) { 532 | if (srv->srv_s == -1) 533 | srv->srv_s = imsg->fd; 534 | else 535 | close(imsg->fd); 536 | } 537 | return (config_getserver_config(env, srv, imsg)); 538 | } 539 | 540 | if (srv_conf.flags & SRVFLAG_LOCATION) 541 | fatalx("invalid location"); 542 | 543 | /* Otherwise create a new server */ 544 | if ((srv = calloc(1, sizeof(*srv))) == NULL) 545 | goto fail; 546 | 547 | memcpy(&srv->srv_conf, &srv_conf, sizeof(srv->srv_conf)); 548 | srv->srv_s = imsg->fd; 549 | 550 | if (config_getserver_auth(env, &srv->srv_conf) != 0) 551 | goto fail; 552 | 553 | SPLAY_INIT(&srv->srv_clients); 554 | TAILQ_INIT(&srv->srv_hosts); 555 | 556 | TAILQ_INSERT_TAIL(&srv->srv_hosts, &srv->srv_conf, entry); 557 | TAILQ_INSERT_TAIL(env->sc_servers, srv, srv_entry); 558 | 559 | DPRINTF("%s: %s %d configuration \"%s[%u]\", flags: %s", __func__, 560 | ps->ps_title[privsep_process], ps->ps_instance, 561 | srv->srv_conf.name, srv->srv_conf.id, 562 | printb_flags(srv->srv_conf.flags, SRVFLAG_BITS)); 563 | 564 | /* 565 | * Get all variable-length values for the parent server. 566 | */ 567 | if (srv->srv_conf.return_uri_len != 0) { 568 | if ((srv->srv_conf.return_uri = get_data(p + s, 569 | srv->srv_conf.return_uri_len)) == NULL) 570 | goto fail; 571 | s += srv->srv_conf.return_uri_len; 572 | } 573 | 574 | return (0); 575 | 576 | fail: 577 | if (imsg->fd != -1) 578 | close(imsg->fd); 579 | if (srv != NULL) 580 | serverconfig_free(&srv->srv_conf); 581 | free(srv); 582 | 583 | return (-1); 584 | } 585 | 586 | static int 587 | config_gettls(struct httpd *env, struct server_config *srv_conf, 588 | struct tls_config *tls_conf, const char *label, uint8_t *data, size_t len, 589 | uint8_t **outdata, size_t *outlen) 590 | { 591 | #ifdef DEBUG 592 | struct privsep *ps = env->sc_ps; 593 | #endif 594 | 595 | DPRINTF("%s: %s %d getting tls %s (%zu:%zu@%zu) for \"%s[%u]\"", 596 | __func__, ps->ps_title[privsep_process], ps->ps_instance, label, 597 | tls_conf->tls_len, len, tls_conf->tls_chunk_offset, srv_conf->name, 598 | srv_conf->id); 599 | 600 | if (tls_conf->tls_chunk_offset == 0) { 601 | free(*outdata); 602 | *outlen = 0; 603 | if ((*outdata = calloc(1, tls_conf->tls_len)) == NULL) 604 | goto fail; 605 | *outlen = tls_conf->tls_len; 606 | } 607 | 608 | if (*outdata == NULL) { 609 | log_debug("%s: tls config invalid chunk sequence", __func__); 610 | goto fail; 611 | } 612 | 613 | if (*outlen != tls_conf->tls_len) { 614 | log_debug("%s: tls config length mismatch (%zu != %zu)", 615 | __func__, *outlen, tls_conf->tls_len); 616 | goto fail; 617 | } 618 | 619 | if (len > (tls_conf->tls_len - tls_conf->tls_chunk_offset)) { 620 | log_debug("%s: tls config invalid chunk length", __func__); 621 | goto fail; 622 | } 623 | 624 | memcpy(*outdata + tls_conf->tls_chunk_offset, data, len); 625 | 626 | return (0); 627 | 628 | fail: 629 | return (-1); 630 | } 631 | 632 | int 633 | config_getserver_tls(struct httpd *env, struct imsg *imsg) 634 | { 635 | struct server_config *srv_conf = NULL; 636 | struct tls_config tls_conf; 637 | uint8_t *p = imsg->data; 638 | size_t len; 639 | 640 | IMSG_SIZE_CHECK(imsg, &tls_conf); 641 | memcpy(&tls_conf, p, sizeof(tls_conf)); 642 | 643 | len = tls_conf.tls_chunk_len; 644 | 645 | if ((IMSG_DATA_SIZE(imsg) - sizeof(tls_conf)) < len) { 646 | log_debug("%s: invalid message length", __func__); 647 | goto fail; 648 | } 649 | 650 | p += sizeof(tls_conf); 651 | 652 | if ((srv_conf = serverconfig_byid(tls_conf.id)) == NULL) { 653 | log_debug("%s: server not found", __func__); 654 | goto fail; 655 | } 656 | 657 | switch (tls_conf.tls_type) { 658 | case TLS_CFG_CERT: 659 | if (config_gettls(env, srv_conf, &tls_conf, "cert", p, len, 660 | &srv_conf->tls_cert, &srv_conf->tls_cert_len) != 0) 661 | goto fail; 662 | break; 663 | 664 | case TLS_CFG_KEY: 665 | if (config_gettls(env, srv_conf, &tls_conf, "key", p, len, 666 | &srv_conf->tls_key, &srv_conf->tls_key_len) != 0) 667 | goto fail; 668 | break; 669 | 670 | case TLS_CFG_OCSP_STAPLE: 671 | if (config_gettls(env, srv_conf, &tls_conf, "ocsp staple", 672 | p, len, &srv_conf->tls_ocsp_staple, 673 | &srv_conf->tls_ocsp_staple_len) != 0) 674 | goto fail; 675 | break; 676 | 677 | default: 678 | log_debug("%s: unknown tls config type %i\n", 679 | __func__, tls_conf.tls_type); 680 | goto fail; 681 | } 682 | 683 | return (0); 684 | 685 | fail: 686 | return (-1); 687 | } 688 | 689 | int 690 | config_setmedia(struct httpd *env, struct media_type *media) 691 | { 692 | struct privsep *ps = env->sc_ps; 693 | int id; 694 | unsigned int what; 695 | 696 | for (id = 0; id < PROC_MAX; id++) { 697 | what = ps->ps_what[id]; 698 | 699 | if ((what & CONFIG_MEDIA) == 0 || id == privsep_process) 700 | continue; 701 | 702 | DPRINTF("%s: sending media \"%s\" to %s", __func__, 703 | media->media_name, ps->ps_title[id]); 704 | 705 | proc_compose(ps, id, IMSG_CFG_MEDIA, media, sizeof(*media)); 706 | } 707 | 708 | return (0); 709 | } 710 | 711 | int 712 | config_getmedia(struct httpd *env, struct imsg *imsg) 713 | { 714 | #ifdef DEBUG 715 | struct privsep *ps = env->sc_ps; 716 | #endif 717 | struct media_type media; 718 | uint8_t *p = imsg->data; 719 | 720 | IMSG_SIZE_CHECK(imsg, &media); 721 | memcpy(&media, p, sizeof(media)); 722 | 723 | if (media_add(env->sc_mediatypes, &media) == NULL) { 724 | log_debug("%s: failed to add media \"%s\"", 725 | __func__, media.media_name); 726 | return (-1); 727 | } 728 | 729 | DPRINTF("%s: %s %d received media \"%s\"", __func__, 730 | ps->ps_title[privsep_process], ps->ps_instance, 731 | media.media_name); 732 | 733 | return (0); 734 | } 735 | 736 | int 737 | config_setauth(struct httpd *env, struct auth *auth) 738 | { 739 | struct privsep *ps = env->sc_ps; 740 | int id; 741 | unsigned int what; 742 | 743 | for (id = 0; id < PROC_MAX; id++) { 744 | what = ps->ps_what[id]; 745 | 746 | if ((what & CONFIG_AUTH) == 0 || id == privsep_process) 747 | continue; 748 | 749 | DPRINTF("%s: sending auth \"%s[%u]\" to %s", __func__, 750 | auth->auth_htpasswd, auth->auth_id, ps->ps_title[id]); 751 | 752 | proc_compose(ps, id, IMSG_CFG_AUTH, auth, sizeof(*auth)); 753 | } 754 | 755 | return (0); 756 | } 757 | 758 | int 759 | config_getauth(struct httpd *env, struct imsg *imsg) 760 | { 761 | #ifdef DEBUG 762 | struct privsep *ps = env->sc_ps; 763 | #endif 764 | struct auth auth; 765 | uint8_t *p = imsg->data; 766 | 767 | IMSG_SIZE_CHECK(imsg, &auth); 768 | memcpy(&auth, p, sizeof(auth)); 769 | 770 | if (auth_add(env->sc_auth, &auth) == NULL) { 771 | log_debug("%s: failed to add auth \"%s[%u]\"", 772 | __func__, auth.auth_htpasswd, auth.auth_id); 773 | return (-1); 774 | } 775 | 776 | DPRINTF("%s: %s %d received auth \"%s[%u]\"", __func__, 777 | ps->ps_title[privsep_process], ps->ps_instance, 778 | auth.auth_htpasswd, auth.auth_id); 779 | 780 | return (0); 781 | } 782 | -------------------------------------------------------------------------------- /httpd/control.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: control.c,v 1.13 2017/01/09 14:49:22 reyk Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 2003, 2004 Henning Brauer 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "httpd.h" 34 | 35 | #define CONTROL_BACKLOG 5 36 | 37 | struct ctl_connlist ctl_conns; 38 | 39 | void control_accept(int, short, void *); 40 | void control_close(int, struct control_sock *); 41 | 42 | int 43 | control_init(struct privsep *ps, struct control_sock *cs) 44 | { 45 | struct httpd *env = ps->ps_env; 46 | struct sockaddr_un sun; 47 | int fd; 48 | mode_t old_umask, mode; 49 | 50 | if (cs->cs_name == NULL) 51 | return (0); 52 | 53 | if ((fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0)) == -1) { 54 | log_warn("%s: socket", __func__); 55 | return (-1); 56 | } 57 | 58 | sun.sun_family = AF_UNIX; 59 | if (strlcpy(sun.sun_path, cs->cs_name, 60 | sizeof(sun.sun_path)) >= sizeof(sun.sun_path)) { 61 | log_warn("%s: %s name too long", __func__, cs->cs_name); 62 | close(fd); 63 | return (-1); 64 | } 65 | 66 | if (unlink(cs->cs_name) == -1) 67 | if (errno != ENOENT) { 68 | log_warn("%s: unlink %s", __func__, cs->cs_name); 69 | close(fd); 70 | return (-1); 71 | } 72 | 73 | if (cs->cs_restricted) { 74 | old_umask = umask(S_IXUSR|S_IXGRP|S_IXOTH); 75 | mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH; 76 | } else { 77 | old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH); 78 | mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP; 79 | } 80 | 81 | if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) { 82 | log_warn("%s: bind: %s", __func__, cs->cs_name); 83 | close(fd); 84 | (void)umask(old_umask); 85 | return (-1); 86 | } 87 | (void)umask(old_umask); 88 | 89 | if (chmod(cs->cs_name, mode) == -1) { 90 | log_warn("%s: chmod", __func__); 91 | close(fd); 92 | (void)unlink(cs->cs_name); 93 | return (-1); 94 | } 95 | 96 | cs->cs_fd = fd; 97 | cs->cs_env = env; 98 | 99 | return (0); 100 | } 101 | 102 | int 103 | control_listen(struct control_sock *cs) 104 | { 105 | if (cs->cs_name == NULL) 106 | return (0); 107 | 108 | if (listen(cs->cs_fd, CONTROL_BACKLOG) == -1) { 109 | log_warn("%s: listen", __func__); 110 | return (-1); 111 | } 112 | 113 | event_set(&cs->cs_ev, cs->cs_fd, EV_READ, 114 | control_accept, cs); 115 | event_add(&cs->cs_ev, NULL); 116 | evtimer_set(&cs->cs_evt, control_accept, cs); 117 | 118 | return (0); 119 | } 120 | 121 | void 122 | control_cleanup(struct control_sock *cs) 123 | { 124 | if (cs->cs_name == NULL) 125 | return; 126 | event_del(&cs->cs_ev); 127 | event_del(&cs->cs_evt); 128 | } 129 | 130 | /* ARGSUSED */ 131 | void 132 | control_accept(int listenfd, short event, void *arg) 133 | { 134 | int connfd; 135 | socklen_t len; 136 | struct sockaddr_un sun; 137 | struct ctl_conn *c; 138 | struct control_sock *cs = arg; 139 | 140 | event_add(&cs->cs_ev, NULL); 141 | if ((event & EV_TIMEOUT)) 142 | return; 143 | 144 | len = sizeof(sun); 145 | if ((connfd = accept4(listenfd, 146 | (struct sockaddr *)&sun, &len, SOCK_NONBLOCK)) == -1) { 147 | /* 148 | * Pause accept if we are out of file descriptors, or 149 | * libevent will haunt us here too. 150 | */ 151 | if (errno == ENFILE || errno == EMFILE) { 152 | struct timeval evtpause = { 1, 0 }; 153 | 154 | event_del(&cs->cs_ev); 155 | evtimer_add(&cs->cs_evt, &evtpause); 156 | } else if (errno != EWOULDBLOCK && errno != EINTR && 157 | errno != ECONNABORTED) 158 | log_warn("%s: accept", __func__); 159 | return; 160 | } 161 | 162 | if ((c = calloc(1, sizeof(struct ctl_conn))) == NULL) { 163 | close(connfd); 164 | log_warn("%s: calloc", __func__); 165 | return; 166 | } 167 | 168 | imsg_init(&c->iev.ibuf, connfd); 169 | c->iev.handler = control_dispatch_imsg; 170 | c->iev.events = EV_READ; 171 | c->iev.data = cs; /* proc.c cheats (reuses the handler) */ 172 | event_set(&c->iev.ev, c->iev.ibuf.fd, c->iev.events, 173 | c->iev.handler, cs); 174 | event_add(&c->iev.ev, NULL); 175 | 176 | TAILQ_INSERT_TAIL(&ctl_conns, c, entry); 177 | } 178 | 179 | struct ctl_conn * 180 | control_connbyfd(int fd) 181 | { 182 | struct ctl_conn *c; 183 | 184 | TAILQ_FOREACH(c, &ctl_conns, entry) { 185 | if (c->iev.ibuf.fd == fd) 186 | break; 187 | } 188 | 189 | return (c); 190 | } 191 | 192 | void 193 | control_close(int fd, struct control_sock *cs) 194 | { 195 | struct ctl_conn *c; 196 | 197 | if ((c = control_connbyfd(fd)) == NULL) { 198 | log_warn("%s: fd %d not found", __func__, fd); 199 | return; 200 | } 201 | 202 | msgbuf_clear(&c->iev.ibuf.w); 203 | TAILQ_REMOVE(&ctl_conns, c, entry); 204 | 205 | event_del(&c->iev.ev); 206 | close(c->iev.ibuf.fd); 207 | 208 | /* Some file descriptors are available again. */ 209 | if (evtimer_pending(&cs->cs_evt, NULL)) { 210 | evtimer_del(&cs->cs_evt); 211 | event_add(&cs->cs_ev, NULL); 212 | } 213 | 214 | free(c); 215 | } 216 | 217 | /* ARGSUSED */ 218 | void 219 | control_dispatch_imsg(int fd, short event, void *arg) 220 | { 221 | struct control_sock *cs = arg; 222 | struct ctl_conn *c; 223 | struct imsg imsg; 224 | int n; 225 | int verbose; 226 | struct httpd *env = cs->cs_env; 227 | 228 | if ((c = control_connbyfd(fd)) == NULL) { 229 | log_warn("%s: fd %d not found", __func__, fd); 230 | return; 231 | } 232 | 233 | if (event & EV_READ) { 234 | if (((n = imsg_read(&c->iev.ibuf)) == -1 && errno != EAGAIN) || 235 | n == 0) { 236 | control_close(fd, cs); 237 | return; 238 | } 239 | } 240 | 241 | if (event & EV_WRITE) { 242 | if (msgbuf_write(&c->iev.ibuf.w) <= 0 && errno != EAGAIN) { 243 | control_close(fd, cs); 244 | return; 245 | } 246 | } 247 | 248 | for (;;) { 249 | if ((n = imsg_get(&c->iev.ibuf, &imsg)) == -1) { 250 | control_close(fd, cs); 251 | return; 252 | } 253 | 254 | if (n == 0) 255 | break; 256 | 257 | if (c->waiting) { 258 | log_debug("%s: unexpected imsg %d", 259 | __func__, imsg.hdr.type); 260 | imsg_free(&imsg); 261 | control_close(fd, cs); 262 | return; 263 | } 264 | 265 | switch (imsg.hdr.type) { 266 | case IMSG_CTL_SHUTDOWN: 267 | case IMSG_CTL_RELOAD: 268 | case IMSG_CTL_REOPEN: 269 | proc_forward_imsg(env->sc_ps, &imsg, PROC_PARENT, -1); 270 | break; 271 | case IMSG_CTL_NOTIFY: 272 | if (c->flags & CTL_CONN_NOTIFY) { 273 | log_debug("%s: " 274 | "client requested notify more than once", 275 | __func__); 276 | imsg_compose_event(&c->iev, IMSG_CTL_FAIL, 277 | 0, env->sc_ps->ps_instance + 1, -1, 278 | NULL, 0); 279 | break; 280 | } 281 | c->flags |= CTL_CONN_NOTIFY; 282 | break; 283 | case IMSG_CTL_VERBOSE: 284 | IMSG_SIZE_CHECK(&imsg, &verbose); 285 | 286 | memcpy(&verbose, imsg.data, sizeof(verbose)); 287 | 288 | proc_forward_imsg(env->sc_ps, &imsg, PROC_PARENT, -1); 289 | proc_forward_imsg(env->sc_ps, &imsg, PROC_SERVER, -1); 290 | 291 | memcpy(imsg.data, &verbose, sizeof(verbose)); 292 | control_imsg_forward(env->sc_ps, &imsg); 293 | log_setverbose(verbose); 294 | break; 295 | default: 296 | log_debug("%s: error handling imsg %d", 297 | __func__, imsg.hdr.type); 298 | break; 299 | } 300 | imsg_free(&imsg); 301 | } 302 | 303 | imsg_event_add(&c->iev); 304 | } 305 | 306 | void 307 | control_imsg_forward(struct privsep *ps, struct imsg *imsg) 308 | { 309 | struct ctl_conn *c; 310 | 311 | TAILQ_FOREACH(c, &ctl_conns, entry) 312 | if (c->flags & CTL_CONN_NOTIFY) 313 | imsg_compose_event(&c->iev, imsg->hdr.type, 314 | 0, ps->ps_instance + 1, -1, imsg->data, 315 | imsg->hdr.len - IMSG_HEADER_SIZE); 316 | } 317 | -------------------------------------------------------------------------------- /httpd/http.h: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: http.h,v 1.14 2016/08/01 21:15:30 benno Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 2012 - 2015 Reyk Floeter 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #ifndef HTTP_H 20 | #define HTTP_H 21 | 22 | #define HTTP_PORT 80 23 | #define HTTPS_PORT 443 24 | 25 | enum httpmethod { 26 | HTTP_METHOD_NONE = 0, 27 | 28 | /* HTTP/1.1, RFC 7231 */ 29 | HTTP_METHOD_GET, 30 | HTTP_METHOD_HEAD, 31 | HTTP_METHOD_POST, 32 | HTTP_METHOD_PUT, 33 | HTTP_METHOD_DELETE, 34 | HTTP_METHOD_OPTIONS, 35 | HTTP_METHOD_TRACE, 36 | HTTP_METHOD_CONNECT, 37 | 38 | /* WebDAV, RFC 4918 */ 39 | HTTP_METHOD_PROPFIND, 40 | HTTP_METHOD_PROPPATCH, 41 | HTTP_METHOD_MKCOL, 42 | HTTP_METHOD_COPY, 43 | HTTP_METHOD_MOVE, 44 | HTTP_METHOD_LOCK, 45 | HTTP_METHOD_UNLOCK, 46 | 47 | /* WebDAV Versioning Extension, RFC 3253 */ 48 | HTTP_METHOD_VERSION_CONTROL, 49 | HTTP_METHOD_REPORT, 50 | HTTP_METHOD_CHECKOUT, 51 | HTTP_METHOD_CHECKIN, 52 | HTTP_METHOD_UNCHECKOUT, 53 | HTTP_METHOD_MKWORKSPACE, 54 | HTTP_METHOD_UPDATE, 55 | HTTP_METHOD_LABEL, 56 | HTTP_METHOD_MERGE, 57 | HTTP_METHOD_BASELINE_CONTROL, 58 | HTTP_METHOD_MKACTIVITY, 59 | 60 | /* WebDAV Ordered Collections, RFC 3648 */ 61 | HTTP_METHOD_ORDERPATCH, 62 | 63 | /* WebDAV Access Control, RFC 3744 */ 64 | HTTP_METHOD_ACL, 65 | 66 | /* WebDAV Redirect Reference Resources, RFC 4437 */ 67 | HTTP_METHOD_MKREDIRECTREF, 68 | HTTP_METHOD_UPDATEREDIRECTREF, 69 | 70 | /* WebDAV Search, RFC 5323 */ 71 | HTTP_METHOD_SEARCH, 72 | 73 | /* PATCH, RFC 5789 */ 74 | HTTP_METHOD_PATCH, 75 | 76 | /* Server response (internal value) */ 77 | HTTP_METHOD_RESPONSE 78 | }; 79 | 80 | struct http_method { 81 | enum httpmethod method_id; 82 | const char *method_name; 83 | }; 84 | #define HTTP_METHODS { \ 85 | { HTTP_METHOD_GET, "GET" }, \ 86 | { HTTP_METHOD_HEAD, "HEAD" }, \ 87 | { HTTP_METHOD_POST, "POST" }, \ 88 | { HTTP_METHOD_PUT, "PUT" }, \ 89 | { HTTP_METHOD_DELETE, "DELETE" }, \ 90 | { HTTP_METHOD_OPTIONS, "OPTIONS" }, \ 91 | { HTTP_METHOD_TRACE, "TRACE" }, \ 92 | { HTTP_METHOD_CONNECT, "CONNECT" }, \ 93 | { HTTP_METHOD_PROPFIND, "PROPFIND" }, \ 94 | { HTTP_METHOD_PROPPATCH, "PROPPATCH" }, \ 95 | { HTTP_METHOD_MKCOL, "MKCOL" }, \ 96 | { HTTP_METHOD_COPY, "COPY" }, \ 97 | { HTTP_METHOD_MOVE, "MOVE" }, \ 98 | { HTTP_METHOD_LOCK, "LOCK" }, \ 99 | { HTTP_METHOD_UNLOCK, "UNLOCK" }, \ 100 | { HTTP_METHOD_VERSION_CONTROL, "VERSION-CONTROL" }, \ 101 | { HTTP_METHOD_REPORT, "REPORT" }, \ 102 | { HTTP_METHOD_CHECKOUT, "CHECKOUT" }, \ 103 | { HTTP_METHOD_CHECKIN, "CHECKIN" }, \ 104 | { HTTP_METHOD_UNCHECKOUT, "UNCHECKOUT" }, \ 105 | { HTTP_METHOD_MKWORKSPACE, "MKWORKSPACE" }, \ 106 | { HTTP_METHOD_UPDATE, "UPDATE" }, \ 107 | { HTTP_METHOD_LABEL, "LABEL" }, \ 108 | { HTTP_METHOD_MERGE, "MERGE" }, \ 109 | { HTTP_METHOD_BASELINE_CONTROL, "BASELINE-CONTROL" }, \ 110 | { HTTP_METHOD_MKACTIVITY, "MKACTIVITY" }, \ 111 | { HTTP_METHOD_ORDERPATCH, "ORDERPATCH" }, \ 112 | { HTTP_METHOD_ACL, "ACL" }, \ 113 | { HTTP_METHOD_MKREDIRECTREF, "MKREDIRECTREF" }, \ 114 | { HTTP_METHOD_UPDATEREDIRECTREF, "UPDATEREDIRECTREF" }, \ 115 | { HTTP_METHOD_SEARCH, "SEARCH" }, \ 116 | { HTTP_METHOD_PATCH, "PATCH" }, \ 117 | { HTTP_METHOD_NONE, NULL } \ 118 | } 119 | 120 | struct http_error { 121 | int error_code; 122 | const char *error_name; 123 | }; 124 | 125 | /* 126 | * HTTP status codes based on IANA assignments (2014-06-11 version): 127 | * https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml 128 | * plus legacy (306) and non-standard (420). 129 | */ 130 | #define HTTP_ERRORS { \ 131 | { 100, "Continue" }, \ 132 | { 101, "Switching Protocols" }, \ 133 | { 102, "Processing" }, \ 134 | /* 103-199 unassigned */ \ 135 | { 200, "OK" }, \ 136 | { 201, "Created" }, \ 137 | { 202, "Accepted" }, \ 138 | { 203, "Non-Authoritative Information" }, \ 139 | { 204, "No Content" }, \ 140 | { 205, "Reset Content" }, \ 141 | { 206, "Partial Content" }, \ 142 | { 207, "Multi-Status" }, \ 143 | { 208, "Already Reported" }, \ 144 | /* 209-225 unassigned */ \ 145 | { 226, "IM Used" }, \ 146 | /* 227-299 unassigned */ \ 147 | { 300, "Multiple Choices" }, \ 148 | { 301, "Moved Permanently" }, \ 149 | { 302, "Found" }, \ 150 | { 303, "See Other" }, \ 151 | { 304, "Not Modified" }, \ 152 | { 305, "Use Proxy" }, \ 153 | { 306, "Switch Proxy" }, \ 154 | { 307, "Temporary Redirect" }, \ 155 | { 308, "Permanent Redirect" }, \ 156 | /* 309-399 unassigned */ \ 157 | { 400, "Bad Request" }, \ 158 | { 401, "Unauthorized" }, \ 159 | { 402, "Payment Required" }, \ 160 | { 403, "Forbidden" }, \ 161 | { 404, "Not Found" }, \ 162 | { 405, "Method Not Allowed" }, \ 163 | { 406, "Not Acceptable" }, \ 164 | { 407, "Proxy Authentication Required" }, \ 165 | { 408, "Request Timeout" }, \ 166 | { 409, "Conflict" }, \ 167 | { 410, "Gone" }, \ 168 | { 411, "Length Required" }, \ 169 | { 412, "Precondition Failed" }, \ 170 | { 413, "Payload Too Large" }, \ 171 | { 414, "URI Too Long" }, \ 172 | { 415, "Unsupported Media Type" }, \ 173 | { 416, "Range Not Satisfiable" }, \ 174 | { 417, "Expectation Failed" }, \ 175 | { 418, "I'm a teapot" }, \ 176 | /* 419-421 unassigned */ \ 177 | { 420, "Enhance Your Calm" }, \ 178 | { 422, "Unprocessable Entity" }, \ 179 | { 423, "Locked" }, \ 180 | { 424, "Failed Dependency" }, \ 181 | /* 425 unassigned */ \ 182 | { 426, "Upgrade Required" }, \ 183 | /* 427 unassigned */ \ 184 | { 428, "Precondition Required" }, \ 185 | { 429, "Too Many Requests" }, \ 186 | /* 430 unassigned */ \ 187 | { 431, "Request Header Fields Too Large" }, \ 188 | /* 432-450 unassigned */ \ 189 | { 451, "Unavailable For Legal Reasons" }, \ 190 | /* 452-499 unassigned */ \ 191 | { 500, "Internal Server Error" }, \ 192 | { 501, "Not Implemented" }, \ 193 | { 502, "Bad Gateway" }, \ 194 | { 503, "Service Unavailable" }, \ 195 | { 504, "Gateway Timeout" }, \ 196 | { 505, "HTTP Version Not Supported" }, \ 197 | { 506, "Variant Also Negotiates" }, \ 198 | { 507, "Insufficient Storage" }, \ 199 | { 508, "Loop Detected" }, \ 200 | /* 509 unassigned */ \ 201 | { 510, "Not Extended" }, \ 202 | { 511, "Network Authentication Required" }, \ 203 | /* 512-599 unassigned */ \ 204 | { 0, NULL } \ 205 | } 206 | 207 | struct http_mediatype { 208 | char *media_name; 209 | char *media_type; 210 | char *media_subtype; 211 | }; 212 | /* 213 | * Some default media types based on (2014-08-04 version): 214 | * https://www.iana.org/assignments/media-types/media-types.xhtml 215 | */ 216 | #define MEDIA_TYPES { \ 217 | { "css", "text", "css" }, \ 218 | { "html", "text", "html" }, \ 219 | { "txt", "text", "plain" }, \ 220 | { "gif", "image", "gif" }, \ 221 | { "jpeg", "image", "jpeg" }, \ 222 | { "jpg", "image", "jpeg" }, \ 223 | { "png", "image", "png" }, \ 224 | { "svg", "image", "svg+xml" }, \ 225 | { "js", "application", "javascript" }, \ 226 | { NULL } \ 227 | } 228 | 229 | /* Used during runtime */ 230 | struct http_descriptor { 231 | struct kv http_pathquery; 232 | struct kv http_matchquery; 233 | #define http_path http_pathquery.kv_key 234 | #define http_query http_pathquery.kv_value 235 | #define http_rescode http_pathquery.kv_key 236 | #define http_resmesg http_pathquery.kv_value 237 | #define query_key http_matchquery.kv_key 238 | #define query_val http_matchquery.kv_value 239 | 240 | char *http_host; 241 | enum httpmethod http_method; 242 | int http_chunked; 243 | char *http_version; 244 | unsigned int http_status; 245 | 246 | /* Rewritten path remains NULL if not used */ 247 | char *http_path_alias; 248 | 249 | /* A tree of headers and attached lists for repeated headers. */ 250 | struct kv *http_lastheader; 251 | struct kvtree http_headers; 252 | }; 253 | 254 | #endif /* HTTP_H */ 255 | -------------------------------------------------------------------------------- /httpd/httpd.8: -------------------------------------------------------------------------------- 1 | .\" $OpenBSD: httpd.8,v 1.53 2016/09/15 20:57:07 jmc Exp $ 2 | .\" 3 | .\" Copyright (c) 2014 Reyk Floeter 4 | .\" 5 | .\" Permission to use, copy, modify, and distribute this software for any 6 | .\" purpose with or without fee is hereby granted, provided that the above 7 | .\" copyright notice and this permission notice appear in all copies. 8 | .\" 9 | .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | .\" 17 | .Dd $Mdocdate: September 15 2016 $ 18 | .Dt HTTPD 8 19 | .Os 20 | .Sh NAME 21 | .Nm httpd 22 | .Nd HTTP daemon 23 | .Sh SYNOPSIS 24 | .Nm 25 | .Op Fl dnv 26 | .Op Fl D Ar macro Ns = Ns Ar value 27 | .Op Fl f Ar file 28 | .Sh DESCRIPTION 29 | The 30 | .Nm 31 | daemon is an HTTP server with FastCGI and TLS support. 32 | .Pp 33 | The FastCGI implementation has optional socket support. 34 | .Nm 35 | can log to 36 | .Xr syslog 3 37 | or per-server files with several standard formats. 38 | .Pp 39 | .Nm 40 | rereads its configuration file when it receives 41 | .Dv SIGHUP 42 | and reopens log files when it receives 43 | .Dv SIGUSR1 . 44 | .Pp 45 | The options are as follows: 46 | .Bl -tag -width Dssmacro=value 47 | .It Fl D Ar macro Ns = Ns Ar value 48 | Set a 49 | .Ar macro 50 | to a 51 | .Ar value . 52 | Macros can be referenced in the configuration files. 53 | .It Fl d 54 | Debug mode. 55 | Create one server and don't detach or become a daemon. 56 | This allows for easy monitoring of 57 | .Nm . 58 | .It Fl f Ar file 59 | Specifies the configuration file. 60 | The default is 61 | .Pa /etc/httpd.conf . 62 | .It Fl n 63 | Check that the configuration is valid, but don't start any servers. 64 | .It Fl v 65 | Verbose mode. 66 | Multiple 67 | .Fl v 68 | options increase the verbosity. 69 | .El 70 | .Sh FILES 71 | .Bl -tag -width "/etc/ssl/private/server.key" -compact 72 | .It Pa /etc/httpd.conf 73 | Default configuration file. 74 | .It Pa /etc/ssl/private/server.key 75 | Default SSL/TLS server key. 76 | .It Pa /etc/ssl/server.crt 77 | Default SSL/TLS server certificate. 78 | .It Pa /var/run/httpd.sock 79 | .Ux Ns -domain 80 | socket used for communication with 81 | .Nm . 82 | .It Pa /var/www/logs/access.log 83 | Default access log file. 84 | .It Pa /var/www/logs/error.log 85 | Default error log file. 86 | .El 87 | .Sh SEE ALSO 88 | .Xr acme-client 1 , 89 | .Xr httpd.conf 5 , 90 | .Xr slowcgi 8 91 | .Sh HISTORY 92 | The 93 | .Nm 94 | program first appeared in 95 | .Ox 5.6 . 96 | .Nm 97 | is based on 98 | .Xr relayd 8 . 99 | .Sh AUTHORS 100 | .An -nosplit 101 | The 102 | .Nm 103 | program was written by 104 | .An Reyk Floeter Aq Mt reyk@openbsd.org . 105 | -------------------------------------------------------------------------------- /httpd/httpd.conf.5: -------------------------------------------------------------------------------- 1 | .\" $OpenBSD: httpd.conf.5,v 1.84 2017/08/11 20:30:45 jmc Exp $ 2 | .\" 3 | .\" Copyright (c) 2014, 2015 Reyk Floeter 4 | .\" 5 | .\" Permission to use, copy, modify, and distribute this software for any 6 | .\" purpose with or without fee is hereby granted, provided that the above 7 | .\" copyright notice and this permission notice appear in all copies. 8 | .\" 9 | .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | .\" 17 | .Dd $Mdocdate: August 11 2017 $ 18 | .Dt HTTPD.CONF 5 19 | .Os 20 | .Sh NAME 21 | .Nm httpd.conf 22 | .Nd HTTP daemon configuration file 23 | .Sh DESCRIPTION 24 | .Nm 25 | is the configuration file for the HTTP daemon, 26 | .Xr httpd 8 . 27 | .Sh SECTIONS 28 | .Nm 29 | is divided into four main sections: 30 | .Bl -tag -width xxxx 31 | .It Sy Macros 32 | User-defined variables may be defined and used later, simplifying the 33 | configuration file. 34 | .It Sy Global Configuration 35 | Global settings for 36 | .Xr httpd 8 . 37 | .It Sy Servers 38 | Listening HTTP web servers. 39 | .It Sy Types 40 | Media types and extensions. 41 | .El 42 | .Pp 43 | Within the sections, 44 | a host 45 | .Ar address 46 | can be specified by IPv4 address, IPv6 address, interface name, 47 | interface group, or DNS hostname. 48 | If the address is an interface name, 49 | .Xr httpd 8 50 | will look up the first IPv4 address and any other IPv4 and IPv6 51 | addresses of the specified network interface. 52 | If 53 | .Sq * 54 | is given as an address, 55 | it will be used as an alias for 56 | .Ar 0.0.0.0 57 | to listen on all IPv4 addresses. 58 | Likewise, 59 | .Sq :: 60 | can be used to listen on all IPv6 addresses. 61 | A 62 | .Ar port 63 | can be specified by number or name. 64 | The port name to number mappings are found in the file 65 | .Pa /etc/services ; 66 | see 67 | .Xr services 5 68 | for details. 69 | .Pp 70 | The current line can be extended over multiple lines using a backslash 71 | .Pq Sq \e . 72 | Comments can be put anywhere in the file using a hash mark 73 | .Pq Sq # , 74 | and extend to the end of the current line. 75 | Care should be taken when commenting out multi-line text: 76 | the comment is effective until the end of the entire block. 77 | .Pp 78 | Argument names not beginning with a letter, digit, or underscore 79 | must be quoted. 80 | .Pp 81 | Additional configuration files can be included with the 82 | .Ic include 83 | keyword, for example: 84 | .Bd -literal -offset indent 85 | include "/etc/httpd.conf.local" 86 | .Ed 87 | .Sh MACROS 88 | Macros can be defined that will later be expanded in context. 89 | Macro names must start with a letter, digit, or underscore, 90 | and may contain any of those characters. 91 | Macro names may not be reserved words (for example, 92 | .Ic directory , 93 | .Ic log , 94 | or 95 | .Ic root ) . 96 | Macros are not expanded inside quotes. 97 | .Pp 98 | For example: 99 | .Bd -literal -offset indent 100 | ext_ip="10.0.0.1" 101 | server "default" { 102 | listen on $ext_ip port 80 103 | } 104 | .Ed 105 | .Sh GLOBAL CONFIGURATION 106 | Here are the settings that can be set globally: 107 | .Bl -tag -width Ds 108 | .It Ic chroot Ar directory 109 | Set the 110 | .Xr chroot 2 111 | directory. 112 | If not specified, it defaults to 113 | .Pa /var/www , 114 | the home directory of the www user. 115 | .It Ic default type Ar type/subtype 116 | Set the default media type that is used if the media type for a 117 | specified extension is not found in the configured types or for files 118 | without a file extension; 119 | see the 120 | .Sx TYPES 121 | section below. 122 | If not specified, the default type is set to 123 | .Ar application/octet-stream . 124 | .It Ic logdir Ar directory 125 | Specifies the full path of the directory in which log files will be written. 126 | If not specified, it defaults to 127 | .Pa /logs 128 | within the 129 | .Xr chroot 2 130 | directory. 131 | .It Ic prefork Ar number 132 | Run the specified number of server processes. 133 | This increases the performance and prevents delays when connecting 134 | to a server. 135 | .Xr httpd 8 136 | runs 3 server processes by default. 137 | .El 138 | .Sh SERVERS 139 | The configured web servers. 140 | .Pp 141 | Each 142 | .Ic server 143 | section starts with a declaration of the server 144 | .Ar name : 145 | .Bl -tag -width Ds 146 | .It Ic server Ar name Brq ... 147 | Match the server name using shell globbing rules. 148 | This can be an explicit name, 149 | .Ar www.example.com , 150 | or a name including wildcards, 151 | .Ar *.example.com . 152 | .It Ic server match Ar name Brq ... 153 | Match the server name using pattern matching, 154 | see 155 | .Xr patterns 7 . 156 | .El 157 | .Pp 158 | Followed by a block of options that is enclosed in curly brackets: 159 | .Bl -tag -width Ds 160 | .It Ic alias Ar name 161 | Specify an additional alias 162 | .Ar name 163 | for this server. 164 | .It Ic alias match Ar name 165 | Like the 166 | .Ic alias 167 | option, 168 | but 169 | .Ic match 170 | the 171 | .Ar name 172 | using pattern matching instead of shell globbing rules, 173 | see 174 | .Xr patterns 7 . 175 | .It Oo Ic no Oc Ic authenticate Oo Ar realm Oc Ic with Pa htpasswd 176 | Authenticate a remote user for 177 | .Ar realm 178 | by checking the credentials against the user authentication file 179 | .Pa htpasswd . 180 | The file name is relative to the 181 | .Ic chroot 182 | and must be readable by the www user. 183 | Use the 184 | .Ic no authenticate 185 | directive to disable authentication in a location. 186 | .It Ic block drop 187 | Drop the connection without sending an error page. 188 | .It Ic block Op Ic return Ar code Op Ar uri 189 | Close the connection and send an error page. 190 | If the optional return code is not specified, 191 | .Xr httpd 8 192 | denies access with a 193 | .Sq 403 Forbidden 194 | response. 195 | The optional 196 | .Ar uri 197 | argument can be used with return codes in the 3xx range to send a 198 | .Sq Location: 199 | header for redirection to a specified URI. 200 | .Pp 201 | The 202 | .Ar uri 203 | may contain predefined macros that will be expanded at runtime: 204 | .Pp 205 | .Bl -tag -width $DOCUMENT_URI -offset indent -compact 206 | .It Ic $DOCUMENT_URI 207 | The request path. 208 | .It Ic $QUERY_STRING 209 | The optional query string of the request. 210 | .It Ic $REMOTE_ADDR 211 | The IP address of the connected client. 212 | .It Ic $REMOTE_PORT 213 | The TCP source port of the connected client. 214 | .It Ic $REMOTE_USER 215 | The remote user for HTTP authentication. 216 | .It Ic $REQUEST_URI 217 | The request path and optional query string. 218 | .It Ic $SERVER_ADDR 219 | The configured IP address of the server. 220 | .It Ic $SERVER_PORT 221 | The configured TCP server port of the server. 222 | .It Ic $SERVER_NAME 223 | The name of the server. 224 | .It Ic $HTTP_HOST 225 | The host from the HTTP Host header. 226 | .It Pf % Ar n 227 | The capture index 228 | .Ar n 229 | of a string that was captured by the enclosing 230 | .Ic location match 231 | option. 232 | .El 233 | .It Ic connection Ar option 234 | Set the specified options and limits for HTTP connections. 235 | Valid options are: 236 | .Bl -tag -width Ds 237 | .It Ic max request body Ar number 238 | Set the maximum body size in bytes that the client can send to the server. 239 | The default value is 1048576 bytes (1M). 240 | .It Ic max requests Ar number 241 | Set the maximum number of requests per persistent HTTP connection. 242 | Persistent connections are negotiated using the Keep-Alive header in 243 | HTTP/1.0 and enabled by default in HTTP/1.1. 244 | The default maximum number of requests per connection is 100. 245 | .It Ic request timeout Ar seconds 246 | Specify the inactivity timeout for HTTP operations between client and server, 247 | for example the maximum time to wait for a request from the client. 248 | The default timeout is 60 seconds (1 minute). 249 | The maximum is 2147483647 seconds (68 years). 250 | .It Ic timeout Ar seconds 251 | Specify the inactivity timeout in seconds for accepted sessions, 252 | for example the maximum time to wait for I/O from the FastCGI backend. 253 | The default timeout is 600 seconds (10 minutes). 254 | The maximum is 2147483647 seconds (68 years). 255 | .El 256 | .It Ic default type Ar type/subtype 257 | Set the default media type for the specified location, 258 | overwriting the global setting. 259 | .It Ic directory Ar option 260 | Set the specified options when serving or accessing directories. 261 | Valid options are: 262 | .Bl -tag -width Ds 263 | .It Oo Ic no Oc Ic auto index 264 | If no index file is found, automatically generate a directory listing. 265 | This is disabled by default. 266 | .It Ic index Ar string 267 | Set the directory index file. 268 | If not specified, it defaults to 269 | .Pa index.html . 270 | .It Ic no index 271 | Disable the directory index. 272 | .Xr httpd 8 273 | will neither display nor generate a directory index. 274 | .El 275 | .It Oo Ic no Oc Ic fastcgi Op Ic socket Ar socket 276 | Enable FastCGI instead of serving files. 277 | The 278 | .Ar socket 279 | is a local path name within the 280 | .Xr chroot 2 281 | root directory of 282 | .Xr httpd 8 283 | and defaults to 284 | .Pa /run/slowcgi.sock . 285 | .Pp 286 | The FastCGI handler will be given the following variables: 287 | .Pp 288 | .Bl -tag -width GATEWAY_INTERFACE -offset indent -compact 289 | .It Ic DOCUMENT_ROOT 290 | The document root in which the script is located as configured by the 291 | .Ic root 292 | option for the server or location that matches the request. 293 | .It Ic GATEWAY_INTERFACE 294 | The revision of the CGI specification used. 295 | .It Ic HTTP_* 296 | Additional HTTP headers the connected client sent in the request, if 297 | any. 298 | .It Ic HTTPS 299 | A variable that is set to 300 | .Qq on 301 | when the server has been configured to use TLS. 302 | This variable is omitted otherwise. 303 | .It Ic REQUEST_URI 304 | The path and optional query string as requested by the connected client. 305 | .It Ic DOCUMENT_URI 306 | The canonicalized request path, possibly with a slash or 307 | directory index file name appended. 308 | This is the same as 309 | .Ic PATH_INFO 310 | appended to 311 | .Ic SCRIPT_NAME . 312 | .It Ic SCRIPT_NAME 313 | The virtual URI path to the script. 314 | .It Ic PATH_INFO 315 | The optional path appended after the script name in the request path. 316 | This variable is an empty string if no path is appended after the 317 | script name. 318 | .It Ic SCRIPT_FILENAME 319 | The absolute, physical path to the script within the 320 | .Xr chroot 2 321 | directory. 322 | .It Ic QUERY_STRING 323 | The optional query string of the request. 324 | This variable is an empty 325 | string if there is no query string in the request. 326 | .It Ic REMOTE_ADDR 327 | The IP address of the connected client. 328 | .It Ic REMOTE_PORT 329 | The TCP source port of the connected client. 330 | .It Ic REMOTE_USER 331 | The remote user when using HTTP authentication. 332 | .It Ic REQUEST_METHOD 333 | The HTTP method the connected client used when making the request. 334 | .It Ic SERVER_ADDR 335 | The configured IP address of the server. 336 | .It Ic SERVER_NAME 337 | The name of the server. 338 | .It Ic SERVER_PORT 339 | The configured TCP server port of the server. 340 | .It Ic SERVER_PROTOCOL 341 | The revision of the HTTP specification used. 342 | .It Ic SERVER_SOFTWARE 343 | The server software name of 344 | .Xr httpd 8 . 345 | .El 346 | .It Ic hsts Oo Ar option Oc 347 | Enable HTTP Strict Transport Security. 348 | Valid options are: 349 | .Bl -tag -width Ds 350 | .It Ic max-age Ar seconds 351 | Set the maximum time in seconds a receiving user agent should regard 352 | this host as an HSTS host. 353 | The default is one year. 354 | .It Ic preload 355 | Confirm and authenticate that the site is permitted to be included in 356 | a browser's preload list. 357 | .It Ic subdomains 358 | Signal to the receiving user agent that this host and all sub domains 359 | of the host's domain should be considered HSTS hosts. 360 | .El 361 | .It Ic listen on Ar address Oo Ic tls Oc Ic port Ar number 362 | Set the listen address and port. 363 | This statement can be specified multiple times. 364 | .It Ic location Ar path Brq ... 365 | Specify server configuration rules for a specific location. 366 | The 367 | .Ar path 368 | argument will be matched against the request path with shell globbing rules. 369 | In case of multiple location statements in the same context, the 370 | first matching location statement will be put into effect, while all 371 | later ones will be ignored. 372 | Therefore it is advisable to match for more specific paths first 373 | and for generic ones later on. 374 | A location section may include most of the server configuration rules 375 | except 376 | .Ic alias , 377 | .Ic connection , 378 | .Ic hsts , 379 | .Ic listen on , 380 | .Ic location , 381 | .Ic tcp 382 | and 383 | .Ic tls . 384 | .It Ic location match Ar path Brq ... 385 | Like the 386 | .Ic location 387 | option, 388 | but 389 | .Ic match 390 | the 391 | .Ar path 392 | using pattern matching instead of shell globbing rules, 393 | see 394 | .Xr patterns 7 . 395 | The pattern may contain captures that can be used in the 396 | .Ar uri 397 | of an enclosed 398 | .Ic block return 399 | option. 400 | .It Oo Ic no Oc Ic log Op Ar option 401 | Set the specified logging options. 402 | Logging is enabled by default using the standard 403 | .Ic access 404 | and 405 | .Ic error 406 | log files, 407 | but can be changed per server or location. 408 | Use the 409 | .Ic no log 410 | directive to disable logging of any requests. 411 | Valid options are: 412 | .Bl -tag -width Ds 413 | .It Ic access Ar name 414 | Set the 415 | .Ar name 416 | of the access log file relative to the log directory. 417 | If not specified, it defaults to 418 | .Pa access.log . 419 | .It Ic error Ar name 420 | Set the 421 | .Ar name 422 | of the error log file relative to the log directory. 423 | If not specified, it defaults to 424 | .Pa error.log . 425 | .It Ic style Ar style 426 | Set the logging style. 427 | The 428 | .Ar style 429 | can be 430 | .Cm common , 431 | .Cm combined 432 | or 433 | .Cm connection . 434 | The styles 435 | .Cm common 436 | and 437 | .Cm combined 438 | write a log entry after each request similar to the standard Apache 439 | and nginx access log formats. 440 | The style 441 | .Cm connection 442 | writes a summarized log entry after each connection, 443 | that can have multiple requests, 444 | similar to the format that is used by 445 | .Xr relayd 8 . 446 | If not specified, the default is 447 | .Cm common . 448 | .It Oo Ic no Oc Ic syslog 449 | Enable or disable logging to 450 | .Xr syslog 3 451 | instead of the log files. 452 | .El 453 | .It Ic pass 454 | Disable any previous 455 | .Ic block 456 | in a location. 457 | .It Ic root Ar option 458 | Configure the document root and options for the request path. 459 | Valid options are: 460 | .Bl -tag -width Ds 461 | .It Ar directory 462 | Set the document root of the server. 463 | The 464 | .Ar directory 465 | is a pathname within the 466 | .Xr chroot 2 467 | root directory of 468 | .Nm httpd . 469 | If not specified, it defaults to 470 | .Pa /htdocs . 471 | .It Ic strip Ar number 472 | Strip 473 | .Ar number 474 | path components from the beginning of the request path before looking 475 | up the stripped-down path at the document root. 476 | .El 477 | .It Ic tcp Ar option 478 | Enable or disable the specified TCP/IP options; see 479 | .Xr tcp 4 480 | and 481 | .Xr ip 4 482 | for more information about the options. 483 | Valid options are: 484 | .Bl -tag -width Ds 485 | .It Ic backlog Ar number 486 | Set the maximum length the queue of pending connections may grow to. 487 | The backlog option is 10 by default and is limited by the 488 | .Va kern.somaxconn 489 | .Xr sysctl 8 490 | variable. 491 | .It Ic ip minttl Ar number 492 | This option for the underlying IP connection may be used to discard packets 493 | with a TTL lower than the specified value. 494 | This can be used to implement the 495 | Generalized TTL Security Mechanism (GTSM) 496 | according to RFC 5082. 497 | .It Ic ip ttl Ar number 498 | Change the default time-to-live value in the IP headers. 499 | .It Oo Ic no Oc Ic nodelay 500 | Enable the TCP NODELAY option for this connection. 501 | This is recommended to avoid delays in the data stream. 502 | .It Oo Ic no Oc Ic sack 503 | Use selective acknowledgements for this connection. 504 | .It Ic socket buffer Ar number 505 | Set the socket-level buffer size for input and output for this 506 | connection. 507 | This will affect the TCP window size. 508 | .El 509 | .It Ic tls Ar option 510 | Set the TLS configuration for the server. 511 | These options are only used if TLS has been enabled via the listen directive. 512 | Valid options are: 513 | .Bl -tag -width Ds 514 | .It Ic certificate Ar file 515 | Specify the certificate to use for this server. 516 | The 517 | .Ar file 518 | should contain a PEM encoded certificate. 519 | The default is 520 | .Pa /etc/ssl/server.crt . 521 | .It Ic ciphers Ar string 522 | Specify the TLS cipher string. 523 | If not specified, the default value 524 | .Qq HIGH:!aNULL 525 | will be used (strong crypto cipher suites without anonymous DH). 526 | See the CIPHERS section of 527 | .Xr openssl 1 528 | for information about SSL/TLS cipher suites and preference lists. 529 | .It Ic dhe Ar params 530 | Specify the DHE parameters to use for DHE cipher suites. 531 | Valid parameter values are none, legacy and auto. 532 | For legacy a fixed key length of 1024 bits is used, whereas for auto the key 533 | length is determined automatically. 534 | The default is none, which disables DHE cipher suites. 535 | .It Ic ecdhe Ar curves 536 | Specify a comma separated list of elliptic curves to use for ECDHE cipher suites, 537 | in order of preference. 538 | The special value of "default" will use the default curves; see 539 | .Xr tls_config_set_ecdhecurves 3 540 | for further details. 541 | .It Ic key Ar file 542 | Specify the private key to use for this server. 543 | The 544 | .Ar file 545 | should contain a PEM encoded private key and reside outside of the 546 | .Xr chroot 2 547 | root directory of 548 | .Nm httpd . 549 | The default is 550 | .Pa /etc/ssl/private/server.key . 551 | .It Ic ocsp Ar file 552 | Specify an OCSP response to be stapled during TLS handshakes 553 | with this server. 554 | The 555 | .Ar file 556 | should contain a DER-format OCSP response retrieved from an 557 | OCSP server for the 558 | .Ar certificate 559 | in use. 560 | The default is to not use OCSP stapling. 561 | .It Ic protocols Ar string 562 | Specify the TLS protocols to enable for this server. 563 | If not specified, the value 564 | .Qq default 565 | will be used (secure protocols; TLSv1.2-only). 566 | Refer to the 567 | .Xr tls_config_parse_protocols 3 568 | function for other valid protocol string values. 569 | .It Ic ticket Ic lifetime Ar seconds 570 | Enable TLS session tickets with a 571 | .Ar seconds 572 | session lifetime. 573 | It is possible to set 574 | .Ar seconds 575 | to default to use the httpd default timeout of 2 hours. 576 | .El 577 | .El 578 | .Sh TYPES 579 | Configure the supported media types. 580 | .Xr httpd 8 581 | will set the 582 | .Ar Content-Type 583 | of the response header based on the file extension listed in the 584 | .Ic types 585 | section. 586 | If not specified, 587 | .Xr httpd 8 588 | will use built-in media types for 589 | .Ar text/css , 590 | .Ar text/html , 591 | .Ar text/plain , 592 | .Ar image/gif , 593 | .Ar image/png , 594 | .Ar image/jpeg , 595 | .Ar image/svg+xml , 596 | and 597 | .Ar application/javascript . 598 | .Pp 599 | The 600 | .Ic types 601 | section must include one or more lines of the following syntax: 602 | .Bl -tag -width Ds 603 | .It Ar type/subtype Ar name Op Ar name ... 604 | Set the media 605 | .Ar type 606 | and 607 | .Ar subtype 608 | to the specified extension 609 | .Ar name . 610 | One or more names can be specified per line. 611 | Each line may end with an optional semicolon. 612 | .It Ic include Ar file 613 | Include types definitions from an external file, for example 614 | .Pa /usr/share/misc/mime.types . 615 | .El 616 | .Sh EXAMPLES 617 | The following example will start one server that is pre-forked two 618 | times and is listening on all local IP addresses. 619 | It additionally defines some media types overriding the defaults. 620 | .Bd -literal -offset indent 621 | prefork 2 622 | 623 | server "default" { 624 | listen on * port 80 625 | } 626 | 627 | types { 628 | text/css css 629 | text/html html htm 630 | text/plain txt 631 | image/gif gif 632 | image/jpeg jpeg jpg 633 | image/png png 634 | application/javascript js 635 | application/xml xml 636 | } 637 | .Ed 638 | .Pp 639 | The server can also be configured to only listen on the primary IP 640 | address of the network interface that is a member of the 641 | .Qq egress 642 | group. 643 | .Bd -literal -offset indent 644 | server "default" { 645 | listen on egress port 80 646 | } 647 | .Ed 648 | .Pp 649 | Multiple servers can be configured to support hosting of different domains. 650 | If the same address is repeated multiple times in the 651 | .Ic listen on 652 | statement, 653 | the server will be matched based on the requested host name. 654 | .Bd -literal -offset indent 655 | server "www.example.com" { 656 | alias "example.com" 657 | listen on * port 80 658 | listen on * tls port 443 659 | root "/htdocs/www.example.com" 660 | } 661 | 662 | server "www.a.example.com" { 663 | listen on 203.0.113.1 port 80 664 | root "/htdocs/www.a.example.com" 665 | } 666 | 667 | server "www.b.example.com" { 668 | listen on 203.0.113.1 port 80 669 | root "/htdocs/www.b.example.com" 670 | } 671 | 672 | server "intranet.example.com" { 673 | listen on 10.0.0.1 port 80 674 | root "/htdocs/intranet.example.com" 675 | } 676 | .Ed 677 | .Pp 678 | Simple redirections can be configured with the 679 | .Ic block 680 | directive: 681 | .Bd -literal -offset indent 682 | server "example.com" { 683 | listen on 10.0.0.1 port 80 684 | block return 301 "http://www.example.com$REQUEST_URI" 685 | } 686 | 687 | server "www.example.com" { 688 | listen on 10.0.0.1 port 80 689 | } 690 | .Ed 691 | .Sh SEE ALSO 692 | .Xr htpasswd 1 , 693 | .Xr patterns 7 , 694 | .Xr httpd 8 , 695 | .Xr ocspcheck 8 , 696 | .Xr slowcgi 8 697 | .Sh AUTHORS 698 | .An -nosplit 699 | The 700 | .Xr httpd 8 701 | program was written by 702 | .An Reyk Floeter Aq Mt reyk@openbsd.org . 703 | -------------------------------------------------------------------------------- /httpd/log.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: log.c,v 1.14 2017/03/21 12:06:55 bluhm Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 2003, 2004 Henning Brauer 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | static int debug; 28 | static int verbose; 29 | const char *log_procname; 30 | 31 | void log_init(int, int); 32 | void log_procinit(const char *); 33 | void log_setverbose(int); 34 | int log_getverbose(void); 35 | void log_warn(const char *, ...) 36 | __attribute__((__format__ (printf, 1, 2))); 37 | void log_warnx(const char *, ...) 38 | __attribute__((__format__ (printf, 1, 2))); 39 | void log_info(const char *, ...) 40 | __attribute__((__format__ (printf, 1, 2))); 41 | void log_debug(const char *, ...) 42 | __attribute__((__format__ (printf, 1, 2))); 43 | void logit(int, const char *, ...) 44 | __attribute__((__format__ (printf, 2, 3))); 45 | void vlog(int, const char *, va_list) 46 | __attribute__((__format__ (printf, 2, 0))); 47 | __dead void fatal(const char *, ...) 48 | __attribute__((__format__ (printf, 1, 2))); 49 | __dead void fatalx(const char *, ...) 50 | __attribute__((__format__ (printf, 1, 2))); 51 | 52 | void 53 | log_init(int n_debug, int facility) 54 | { 55 | extern char *__progname; 56 | 57 | debug = n_debug; 58 | verbose = n_debug; 59 | log_procinit(__progname); 60 | 61 | if (!debug) 62 | openlog(__progname, LOG_PID | LOG_NDELAY, facility); 63 | 64 | tzset(); 65 | } 66 | 67 | void 68 | log_procinit(const char *procname) 69 | { 70 | if (procname != NULL) 71 | log_procname = procname; 72 | } 73 | 74 | void 75 | log_setverbose(int v) 76 | { 77 | verbose = v; 78 | } 79 | 80 | int 81 | log_getverbose(void) 82 | { 83 | return (verbose); 84 | } 85 | 86 | void 87 | logit(int pri, const char *fmt, ...) 88 | { 89 | va_list ap; 90 | 91 | va_start(ap, fmt); 92 | vlog(pri, fmt, ap); 93 | va_end(ap); 94 | } 95 | 96 | void 97 | vlog(int pri, const char *fmt, va_list ap) 98 | { 99 | char *nfmt; 100 | int saved_errno = errno; 101 | 102 | if (debug) { 103 | /* best effort in out of mem situations */ 104 | if (asprintf(&nfmt, "%s\n", fmt) == -1) { 105 | vfprintf(stderr, fmt, ap); 106 | fprintf(stderr, "\n"); 107 | } else { 108 | vfprintf(stderr, nfmt, ap); 109 | free(nfmt); 110 | } 111 | fflush(stderr); 112 | } else 113 | vsyslog(pri, fmt, ap); 114 | 115 | errno = saved_errno; 116 | } 117 | 118 | void 119 | log_warn(const char *emsg, ...) 120 | { 121 | char *nfmt; 122 | va_list ap; 123 | int saved_errno = errno; 124 | 125 | /* best effort to even work in out of memory situations */ 126 | if (emsg == NULL) 127 | logit(LOG_ERR, "%s", strerror(saved_errno)); 128 | else { 129 | va_start(ap, emsg); 130 | 131 | if (asprintf(&nfmt, "%s: %s", emsg, 132 | strerror(saved_errno)) == -1) { 133 | /* we tried it... */ 134 | vlog(LOG_ERR, emsg, ap); 135 | logit(LOG_ERR, "%s", strerror(saved_errno)); 136 | } else { 137 | vlog(LOG_ERR, nfmt, ap); 138 | free(nfmt); 139 | } 140 | va_end(ap); 141 | } 142 | 143 | errno = saved_errno; 144 | } 145 | 146 | void 147 | log_warnx(const char *emsg, ...) 148 | { 149 | va_list ap; 150 | 151 | va_start(ap, emsg); 152 | vlog(LOG_ERR, emsg, ap); 153 | va_end(ap); 154 | } 155 | 156 | void 157 | log_info(const char *emsg, ...) 158 | { 159 | va_list ap; 160 | 161 | va_start(ap, emsg); 162 | vlog(LOG_INFO, emsg, ap); 163 | va_end(ap); 164 | } 165 | 166 | void 167 | log_debug(const char *emsg, ...) 168 | { 169 | va_list ap; 170 | 171 | if (verbose > 1) { 172 | va_start(ap, emsg); 173 | vlog(LOG_DEBUG, emsg, ap); 174 | va_end(ap); 175 | } 176 | } 177 | 178 | static void 179 | vfatalc(int code, const char *emsg, va_list ap) 180 | { 181 | static char s[BUFSIZ]; 182 | const char *sep; 183 | 184 | if (emsg != NULL) { 185 | (void)vsnprintf(s, sizeof(s), emsg, ap); 186 | sep = ": "; 187 | } else { 188 | s[0] = '\0'; 189 | sep = ""; 190 | } 191 | if (code) 192 | logit(LOG_CRIT, "%s: %s%s%s", 193 | log_procname, s, sep, strerror(code)); 194 | else 195 | logit(LOG_CRIT, "%s%s%s", log_procname, sep, s); 196 | } 197 | 198 | void 199 | fatal(const char *emsg, ...) 200 | { 201 | va_list ap; 202 | 203 | va_start(ap, emsg); 204 | vfatalc(errno, emsg, ap); 205 | va_end(ap); 206 | exit(1); 207 | } 208 | 209 | void 210 | fatalx(const char *emsg, ...) 211 | { 212 | va_list ap; 213 | 214 | va_start(ap, emsg); 215 | vfatalc(0, emsg, ap); 216 | va_end(ap); 217 | exit(1); 218 | } 219 | -------------------------------------------------------------------------------- /httpd/logger.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: logger.c,v 1.20 2016/09/01 10:59:38 reyk Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 2014 Reyk Floeter 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "httpd.h" 32 | 33 | int logger_dispatch_parent(int, struct privsep_proc *, 34 | struct imsg *); 35 | int logger_dispatch_server(int, struct privsep_proc *, 36 | struct imsg *); 37 | void logger_shutdown(void); 38 | void logger_close(void); 39 | struct log_file *logger_open_file(const char *); 40 | int logger_open_fd(struct imsg *); 41 | int logger_open(struct server *, struct server_config *, void *); 42 | void logger_init(struct privsep *, struct privsep_proc *p, void *); 43 | int logger_start(void); 44 | int logger_log(struct imsg *); 45 | 46 | static uint32_t last_log_id = 0; 47 | 48 | static struct privsep_proc procs[] = { 49 | { "parent", PROC_PARENT, logger_dispatch_parent }, 50 | { "server", PROC_SERVER, logger_dispatch_server } 51 | }; 52 | 53 | void 54 | logger(struct privsep *ps, struct privsep_proc *p) 55 | { 56 | proc_run(ps, p, procs, nitems(procs), logger_init, NULL); 57 | } 58 | 59 | void 60 | logger_shutdown(void) 61 | { 62 | logger_close(); 63 | config_purge(httpd_env, CONFIG_ALL); 64 | } 65 | 66 | void 67 | logger_init(struct privsep *ps, struct privsep_proc *p, void *arg) 68 | { 69 | if (pledge("stdio recvfd", NULL) == -1) 70 | fatal("pledge"); 71 | 72 | if (config_init(ps->ps_env) == -1) 73 | fatal("failed to initialize configuration"); 74 | 75 | /* We use a custom shutdown callback */ 76 | p->p_shutdown = logger_shutdown; 77 | 78 | TAILQ_INIT(&log_files); 79 | } 80 | 81 | void 82 | logger_close(void) 83 | { 84 | struct log_file *log, *next; 85 | 86 | TAILQ_FOREACH_SAFE(log, &log_files, log_entry, next) { 87 | if (log->log_fd != -1) { 88 | close(log->log_fd); 89 | log->log_fd = -1; 90 | } 91 | TAILQ_REMOVE(&log_files, log, log_entry); 92 | } 93 | } 94 | 95 | struct log_file * 96 | logger_open_file(const char *name) 97 | { 98 | struct log_file *log; 99 | struct iovec iov[2]; 100 | 101 | if ((log = calloc(1, sizeof(*log))) == NULL) { 102 | log_warn("failed to allocate log %s", name); 103 | return (NULL); 104 | } 105 | 106 | log->log_id = ++last_log_id; 107 | (void)strlcpy(log->log_name, name, sizeof(log->log_name)); 108 | 109 | /* The file will be opened by the parent process */ 110 | log->log_fd = -1; 111 | 112 | iov[0].iov_base = &log->log_id; 113 | iov[0].iov_len = sizeof(log->log_id); 114 | iov[1].iov_base = log->log_name; 115 | iov[1].iov_len = strlen(log->log_name) + 1; 116 | 117 | if (proc_composev(httpd_env->sc_ps, PROC_PARENT, IMSG_LOG_OPEN, 118 | iov, 2) != 0) { 119 | log_warn("%s: failed to compose IMSG_LOG_OPEN imsg", __func__); 120 | goto err; 121 | } 122 | 123 | TAILQ_INSERT_TAIL(&log_files, log, log_entry); 124 | 125 | return (log); 126 | 127 | err: 128 | free(log); 129 | 130 | return (NULL); 131 | } 132 | 133 | int 134 | logger_open_fd(struct imsg *imsg) 135 | { 136 | struct log_file *log; 137 | uint32_t id; 138 | 139 | IMSG_SIZE_CHECK(imsg, &id); 140 | memcpy(&id, imsg->data, sizeof(id)); 141 | 142 | TAILQ_FOREACH(log, &log_files, log_entry) { 143 | if (log->log_id == id) { 144 | DPRINTF("%s: received log fd %d, file %s", 145 | __func__, imsg->fd, log->log_name); 146 | log->log_fd = imsg->fd; 147 | return (0); 148 | } 149 | } 150 | 151 | return (-1); 152 | } 153 | 154 | int 155 | logger_open_priv(struct imsg *imsg) 156 | { 157 | char path[PATH_MAX]; 158 | char name[NAME_MAX], *p; 159 | uint32_t id; 160 | size_t len; 161 | int fd; 162 | 163 | /* called from the priviled process */ 164 | IMSG_SIZE_CHECK(imsg, &id); 165 | memcpy(&id, imsg->data, sizeof(id)); 166 | p = (char *)imsg->data + sizeof(id); 167 | 168 | if ((size_t)snprintf(name, sizeof(name), "/%s", p) >= sizeof(name)) 169 | return (-1); 170 | if ((len = strlcpy(path, httpd_env->sc_logdir, sizeof(path))) 171 | >= sizeof(path)) 172 | return (-1); 173 | 174 | p = path + len; 175 | len = sizeof(path) - len; 176 | 177 | if (canonicalize_path(name, p, len) == NULL) { 178 | log_warnx("invalid log name"); 179 | return (-1); 180 | } 181 | 182 | if ((fd = open(path, O_WRONLY|O_APPEND|O_CREAT, 0644)) == -1) { 183 | log_warn("failed to open %s", path); 184 | return (-1); 185 | } 186 | 187 | proc_compose_imsg(httpd_env->sc_ps, PROC_LOGGER, -1, 188 | IMSG_LOG_OPEN, -1, fd, &id, sizeof(id)); 189 | 190 | DPRINTF("%s: opened log file %s, fd %d", __func__, path, fd); 191 | 192 | return (0); 193 | } 194 | 195 | int 196 | logger_open(struct server *srv, struct server_config *srv_conf, void *arg) 197 | { 198 | struct log_file *log, *logfile = NULL, *errfile = NULL; 199 | 200 | if (srv_conf->flags & SRVFLAG_SYSLOG) 201 | return (0); 202 | 203 | /* disassociate */ 204 | srv_conf->logaccess = srv_conf->logerror = NULL; 205 | 206 | TAILQ_FOREACH(log, &log_files, log_entry) { 207 | if (strcmp(log->log_name, srv_conf->accesslog) == 0) 208 | logfile = log; 209 | if (strcmp(log->log_name, srv_conf->errorlog) == 0) 210 | errfile = log; 211 | } 212 | 213 | if (logfile == NULL) { 214 | if ((srv_conf->logaccess = 215 | logger_open_file(srv_conf->accesslog)) == NULL) 216 | return (-1); 217 | } else 218 | srv_conf->logaccess = logfile; 219 | 220 | if (errfile == NULL) { 221 | if ((srv_conf->logerror = 222 | logger_open_file(srv_conf->errorlog)) == NULL) 223 | return (-1); 224 | } else 225 | srv_conf->logerror = errfile; 226 | 227 | return (0); 228 | } 229 | 230 | int 231 | logger_start(void) 232 | { 233 | logger_close(); 234 | if (server_foreach(logger_open, NULL) == -1) 235 | fatalx("failed to open log files"); 236 | return (0); 237 | } 238 | 239 | int 240 | logger_log(struct imsg *imsg) 241 | { 242 | char *logline; 243 | uint32_t id; 244 | struct server_config *srv_conf; 245 | struct log_file *log; 246 | 247 | IMSG_SIZE_CHECK(imsg, &id); 248 | memcpy(&id, imsg->data, sizeof(id)); 249 | 250 | if ((srv_conf = serverconfig_byid(id)) == NULL) 251 | fatalx("invalid logging requestr"); 252 | 253 | if (imsg->hdr.type == IMSG_LOG_ACCESS) 254 | log = srv_conf->logaccess; 255 | else 256 | log = srv_conf->logerror; 257 | 258 | if (log == NULL || log->log_fd == -1) { 259 | log_warnx("log file %s not opened", log ? log->log_name : ""); 260 | return (0); 261 | } 262 | 263 | /* XXX get_string() would sanitize the string, but add a malloc */ 264 | logline = (char *)imsg->data + sizeof(id); 265 | 266 | /* For debug output */ 267 | log_debug("%s", logline); 268 | 269 | if (dprintf(log->log_fd, "%s\n", logline) == -1) { 270 | if (logger_start() == -1) 271 | return (-1); 272 | } 273 | 274 | return (0); 275 | } 276 | 277 | int 278 | logger_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg) 279 | { 280 | switch (imsg->hdr.type) { 281 | case IMSG_CFG_SERVER: 282 | config_getserver(httpd_env, imsg); 283 | break; 284 | case IMSG_CFG_DONE: 285 | config_getcfg(httpd_env, imsg); 286 | break; 287 | case IMSG_CTL_START: 288 | case IMSG_CTL_REOPEN: 289 | logger_start(); 290 | break; 291 | case IMSG_CTL_RESET: 292 | config_getreset(httpd_env, imsg); 293 | break; 294 | case IMSG_LOG_OPEN: 295 | return (logger_open_fd(imsg)); 296 | default: 297 | return (-1); 298 | } 299 | 300 | return (0); 301 | } 302 | 303 | int 304 | logger_dispatch_server(int fd, struct privsep_proc *p, struct imsg *imsg) 305 | { 306 | switch (imsg->hdr.type) { 307 | case IMSG_LOG_ACCESS: 308 | case IMSG_LOG_ERROR: 309 | logger_log(imsg); 310 | break; 311 | default: 312 | return (-1); 313 | } 314 | 315 | return (0); 316 | } 317 | -------------------------------------------------------------------------------- /httpd/patterns.7: -------------------------------------------------------------------------------- 1 | .\" $OpenBSD: patterns.7,v 1.6 2017/06/10 13:31:45 schwarze Exp $ 2 | .\" 3 | .\" Copyright (c) 2015 Reyk Floeter 4 | .\" Copyright (C) 1994-2015 Lua.org, PUC-Rio. 5 | .\" 6 | .\" Permission is hereby granted, free of charge, to any person obtaining 7 | .\" a copy of this software and associated documentation files (the 8 | .\" "Software"), to deal in the Software without restriction, including 9 | .\" without limitation the rights to use, copy, modify, merge, publish, 10 | .\" distribute, sublicense, and/or sell copies of the Software, and to 11 | .\" permit persons to whom the Software is furnished to do so, subject to 12 | .\" the following conditions: 13 | .\" 14 | .\" The above copyright notice and this permission notice shall be 15 | .\" included in all copies or substantial portions of the Software. 16 | .\" 17 | .\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | .\" EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | .\" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | .\" IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | .\" CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | .\" TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | .\" SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | .\" 25 | .\" Derived from section 6.4.1 in manual.html of Lua 5.3.1: 26 | .\" $Id: patterns.7,v 1.6 2017/06/10 13:31:45 schwarze Exp $ 27 | .\" 28 | .Dd $Mdocdate: June 10 2017 $ 29 | .Dt PATTERNS 7 30 | .Os 31 | .Sh NAME 32 | .Nm patterns 33 | .Nd Lua's pattern matching rules 34 | .Sh DESCRIPTION 35 | Pattern matching in 36 | .Xr httpd 8 37 | is based on the implementation of the Lua scripting language and 38 | provides a simple and fast alternative to the regular expressions (REs) that 39 | are described in 40 | .Xr re_format 7 . 41 | Patterns are described by regular strings, which are interpreted as 42 | patterns by the pattern-matching 43 | .Dq find 44 | and 45 | .Dq match 46 | functions. 47 | This document describes the syntax and the meaning (that is, what they 48 | match) of these strings. 49 | .Sh CHARACTER CLASS 50 | A character class is used to represent a set of characters. 51 | The following combinations are allowed in describing a character 52 | class: 53 | .Bl -tag -width Ds 54 | .It Ar x 55 | (where 56 | .Ar x 57 | is not one of the magic characters 58 | .Sq ^$()%.[]*+-? ) 59 | represents the character 60 | .Ar x 61 | itself. 62 | .It . 63 | (a dot) represents all characters. 64 | .It %a 65 | represents all letters. 66 | .It %c 67 | represents all control characters. 68 | .It %d 69 | represents all digits. 70 | .It %g 71 | represents all printable characters except space. 72 | .It %l 73 | represents all lowercase letters. 74 | .It %p 75 | represents all punctuation characters. 76 | .It %s 77 | represents all space characters. 78 | .It %u 79 | represents all uppercase letters. 80 | .It %w 81 | represents all alphanumeric characters. 82 | .It %x 83 | represents all hexadecimal digits. 84 | .It Pf % Ar x 85 | (where 86 | .Ar x 87 | is any non-alphanumeric character) represents the character 88 | .Ar x . 89 | This is the standard way to escape the magic characters. 90 | Any non-alphanumeric character (including all punctuation characters, 91 | even the non-magical) can be preceded by a 92 | .Sq % 93 | when used to represent itself in a pattern. 94 | .It Bq Ar set 95 | represents the class which is the union of all 96 | characters in 97 | .Ar set . 98 | A range of characters can be specified by separating the end 99 | characters of the range, in ascending order, with a 100 | .Sq - . 101 | All classes 102 | .Sq Ar %x 103 | described above can also be used as components in 104 | .Ar set . 105 | All other characters in 106 | .Ar set 107 | represent themselves. 108 | For example, 109 | .Sq [%w_] 110 | (or 111 | .Sq [_%w] ) 112 | represents all alphanumeric characters plus the underscore, 113 | .Sq [0-7] 114 | represents the octal digits, 115 | and 116 | .Sq [0-7%l%-] 117 | represents the octal digits plus the lowercase letters plus the 118 | .Sq - 119 | character. 120 | .Pp 121 | The interaction between ranges and classes is not defined. 122 | Therefore, patterns like 123 | .Sq [%a-z] 124 | or 125 | .Sq [a-%%] 126 | have no meaning. 127 | .It Bq Ar ^set 128 | represents the complement of 129 | .Ar set , 130 | where 131 | .Ar set 132 | is interpreted as above. 133 | .El 134 | .Pp 135 | For all classes represented by single letters ( 136 | .Sq %a , 137 | .Sq %c , 138 | etc.), 139 | the corresponding uppercase letter represents the complement of the class. 140 | For instance, 141 | .Sq %S 142 | represents all non-space characters. 143 | .Pp 144 | The definitions of letter, space, and other character groups depend on 145 | the current locale. 146 | In particular, the class 147 | .Sq [a-z] 148 | may not be equivalent to 149 | .Sq %l . 150 | .Sh PATTERN ITEM 151 | A pattern item can be 152 | .Bl -bullet 153 | .It 154 | a single character class, which matches any single character in the class; 155 | .It 156 | a single character class followed by 157 | .Sq * , 158 | which matches zero or more repetitions of characters in the class. 159 | These repetition items will always match the longest possible sequence; 160 | .It 161 | a single character class followed by 162 | .Sq + , 163 | which matches one or more repetitions of characters in the class. 164 | These repetition items will always match the longest possible sequence; 165 | .It 166 | a single character class followed by 167 | .Sq - , 168 | which also matches zero or more repetitions of characters in the class. 169 | Unlike 170 | .Sq * , 171 | these repetition items will always match the shortest possible sequence; 172 | .It 173 | a single character class followed by 174 | .Sq \&? , 175 | which matches zero or one occurrence of a character in the class. 176 | It always matches one occurrence if possible; 177 | .It 178 | .Sq Pf % Ar n , 179 | for 180 | .Ar n 181 | between 1 and 9; 182 | such item matches a substring equal to the n-th captured string (see below); 183 | .It 184 | .Sq Pf %b Ar xy , 185 | where 186 | .Ar x 187 | and 188 | .Ar y 189 | are two distinct characters; 190 | such item matches strings that start with 191 | .Ar x , 192 | end with 193 | .Ar y , 194 | and where the 195 | .Ar x 196 | and 197 | .Ar y 198 | are 199 | .Em balanced . 200 | This means that if one reads the string from left to right, counting 201 | .Em +1 202 | for an 203 | .Ar x 204 | and 205 | .Em -1 206 | for a 207 | .Ar y , 208 | the ending 209 | .Ar y 210 | is the first 211 | .Ar y 212 | where the count reaches 0. 213 | For instance, the item 214 | .Sq %b() 215 | matches expressions with balanced parentheses. 216 | .It 217 | .Sq Pf %f Bq Ar set , 218 | a 219 | .Em frontier pattern ; 220 | such item matches an empty string at any position such that the next 221 | character belongs to 222 | .Ar set 223 | and the previous character does not belong to 224 | .Ar set . 225 | The set 226 | .Ar set 227 | is interpreted as previously described. 228 | The beginning and the end of the subject are handled as if 229 | they were the character 230 | .Sq \e0 . 231 | .El 232 | .Sh PATTERN 233 | A pattern is a sequence of pattern items. 234 | A caret 235 | .Sq ^ 236 | at the beginning of a pattern anchors the match at the beginning of 237 | the subject string. 238 | A 239 | .Sq $ 240 | at the end of a pattern anchors the match at the end of the subject string. 241 | At other positions, 242 | .Sq ^ 243 | and 244 | .Sq $ 245 | have no special meaning and represent themselves. 246 | .Sh CAPTURES 247 | A pattern can contain sub-patterns enclosed in parentheses; they 248 | describe captures. 249 | When a match succeeds, the substrings of the subject string that match 250 | captures are stored (captured) for future use. 251 | Captures are numbered according to their left parentheses. 252 | For instance, in the pattern 253 | .Qq (a*(.)%w(%s*)) , 254 | the part of the string matching 255 | .Qq a*(.)%w(%s*) 256 | is stored as the first capture (and therefore has number 1); 257 | the character matching 258 | .Qq \&. 259 | is captured with number 2, 260 | and the part matching 261 | .Qq %s* 262 | has number 3. 263 | .Pp 264 | As a special case, the empty capture 265 | .Sq () 266 | captures the current string position (a number). 267 | For instance, if we apply the pattern 268 | .Qq ()aa() 269 | on the string 270 | .Qq flaaap , 271 | there will be two captures: 2 and 4. 272 | .Sh SEE ALSO 273 | .Xr fnmatch 3 , 274 | .Xr re_format 7 , 275 | .Xr httpd 8 276 | .Rs 277 | .%A Roberto Ierusalimschy 278 | .%A Luiz Henrique de Figueiredo 279 | .%A Waldemar Celes 280 | .%Q Lua.org 281 | .%Q PUC-Rio 282 | .%D June 2015 283 | .%R Lua 5.3 Reference Manual 284 | .%T Patterns 285 | .%U http://www.lua.org/manual/5.3/manual.html#6.4.1 286 | .Re 287 | .Sh HISTORY 288 | The first implementation of the pattern rules were introduced with Lua 2.5. 289 | Almost twenty years later, 290 | an implementation based on Lua 5.3.1 appeared in 291 | .Ox 5.8 . 292 | .Sh AUTHORS 293 | The pattern matching is derived from the original implementation of 294 | the Lua scripting language written by 295 | .An -nosplit 296 | .An Roberto Ierusalimschy , 297 | .An Waldemar Celes , 298 | and 299 | .An Luiz Henrique de Figueiredo 300 | at PUC-Rio. 301 | It was turned into a native C API for 302 | .Xr httpd 8 303 | by 304 | .An Reyk Floeter Aq Mt reyk@openbsd.org . 305 | .Sh CAVEATS 306 | A notable difference with the Lua implementation is the position in the string 307 | returned by captures. 308 | It follows the C-style indexing (position starting from 0) 309 | instead of Lua-style indexing (position starting from 1). 310 | -------------------------------------------------------------------------------- /httpd/patterns.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: patterns.c,v 1.5 2016/02/14 18:20:59 semarie Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 2015 Reyk Floeter 5 | * Copyright (C) 1994-2015 Lua.org, PUC-Rio. 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining 8 | * a copy of this software and associated documentation files (the 9 | * "Software"), to deal in the Software without restriction, including 10 | * without limitation the rights to use, copy, modify, merge, publish, 11 | * distribute, sublicense, and/or sell copies of the Software, and to 12 | * permit persons to whom the Software is furnished to do so, subject to 13 | * the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be 16 | * included in all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | */ 26 | 27 | /* 28 | * Derived from Lua 5.3.1: 29 | * $Id: patterns.c,v 1.5 2016/02/14 18:20:59 semarie Exp $ 30 | * Standard library for string operations and pattern-matching 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #include "patterns.h" 41 | 42 | #define uchar(c) ((unsigned char)(c)) /* macro to 'unsign' a char */ 43 | #define CAP_UNFINISHED (-1) 44 | #define CAP_POSITION (-2) 45 | #define L_ESC '%' 46 | #define SPECIALS "^$*+?.([%-" 47 | 48 | struct match_state { 49 | int matchdepth; /* control for recursive depth (to avoid C 50 | * stack overflow) */ 51 | int repetitioncounter; /* control the repetition items */ 52 | int maxcaptures; /* configured capture limit */ 53 | const char *src_init; /* init of source string */ 54 | const char *src_end; /* end ('\0') of source string */ 55 | const char *p_end; /* end ('\0') of pattern */ 56 | const char *error; /* should be NULL */ 57 | int level; /* total number of captures (finished or 58 | * unfinished) */ 59 | struct { 60 | const char *init; 61 | ptrdiff_t len; 62 | } capture[MAXCAPTURES]; 63 | }; 64 | 65 | /* recursive function */ 66 | static const char *match(struct match_state *, const char *, const char *); 67 | 68 | static int 69 | match_error(struct match_state *ms, const char *error) 70 | { 71 | ms->error = ms->error == NULL ? error : ms->error; 72 | return (-1); 73 | } 74 | 75 | static int 76 | check_capture(struct match_state *ms, int l) 77 | { 78 | l -= '1'; 79 | if (l < 0 || l >= ms->level || ms->capture[l].len == CAP_UNFINISHED) 80 | return match_error(ms, "invalid capture index"); 81 | return (l); 82 | } 83 | 84 | static int 85 | capture_to_close(struct match_state *ms) 86 | { 87 | int level = ms->level; 88 | for (level--; level >= 0; level--) 89 | if (ms->capture[level].len == CAP_UNFINISHED) 90 | return (level); 91 | return match_error(ms, "invalid pattern capture"); 92 | } 93 | 94 | static const char * 95 | classend(struct match_state *ms, const char *p) 96 | { 97 | switch (*p++) { 98 | case L_ESC: 99 | if (p == ms->p_end) 100 | match_error(ms, 101 | "malformed pattern (ends with '%')"); 102 | return p + 1; 103 | case '[': 104 | if (*p == '^') 105 | p++; 106 | do { 107 | /* look for a ']' */ 108 | if (p == ms->p_end) { 109 | match_error(ms, 110 | "malformed pattern (missing ']')"); 111 | break; 112 | } 113 | if (*(p++) == L_ESC && p < ms->p_end) { 114 | /* skip escapes (e.g. '%]') */ 115 | p++; 116 | } 117 | } while (*p != ']'); 118 | return p + 1; 119 | default: 120 | return p; 121 | } 122 | } 123 | 124 | static int 125 | match_class(int c, int cl) 126 | { 127 | int res; 128 | switch (tolower(cl)) { 129 | case 'a': 130 | res = isalpha(c); 131 | break; 132 | case 'c': 133 | res = iscntrl(c); 134 | break; 135 | case 'd': 136 | res = isdigit(c); 137 | break; 138 | case 'g': 139 | res = isgraph(c); 140 | break; 141 | case 'l': 142 | res = islower(c); 143 | break; 144 | case 'p': 145 | res = ispunct(c); 146 | break; 147 | case 's': 148 | res = isspace(c); 149 | break; 150 | case 'u': 151 | res = isupper(c); 152 | break; 153 | case 'w': 154 | res = isalnum(c); 155 | break; 156 | case 'x': 157 | res = isxdigit(c); 158 | break; 159 | default: 160 | return (cl == c); 161 | } 162 | return (islower(cl) ? res : !res); 163 | } 164 | 165 | static int 166 | matchbracketclass(int c, const char *p, const char *ec) 167 | { 168 | int sig = 1; 169 | if (*(p + 1) == '^') { 170 | sig = 0; 171 | /* skip the '^' */ 172 | p++; 173 | } 174 | while (++p < ec) { 175 | if (*p == L_ESC) { 176 | p++; 177 | if (match_class(c, uchar(*p))) 178 | return sig; 179 | } else if ((*(p + 1) == '-') && (p + 2 < ec)) { 180 | p += 2; 181 | if (uchar(*(p - 2)) <= c && c <= uchar(*p)) 182 | return sig; 183 | } else if (uchar(*p) == c) 184 | return sig; 185 | } 186 | return !sig; 187 | } 188 | 189 | static int 190 | singlematch(struct match_state *ms, const char *s, const char *p, 191 | const char *ep) 192 | { 193 | if (s >= ms->src_end) 194 | return 0; 195 | else { 196 | int c = uchar(*s); 197 | switch (*p) { 198 | case '.': 199 | /* matches any char */ 200 | return (1); 201 | case L_ESC: 202 | return match_class(c, uchar(*(p + 1))); 203 | case '[': 204 | return matchbracketclass(c, p, ep - 1); 205 | default: 206 | return (uchar(*p) == c); 207 | } 208 | } 209 | } 210 | 211 | static const char * 212 | matchbalance(struct match_state *ms, const char *s, const char *p) 213 | { 214 | if (p >= ms->p_end - 1) { 215 | match_error(ms, 216 | "malformed pattern (missing arguments to '%b')"); 217 | return (NULL); 218 | } 219 | if (*s != *p) 220 | return (NULL); 221 | else { 222 | int b = *p; 223 | int e = *(p + 1); 224 | int cont = 1; 225 | while (++s < ms->src_end) { 226 | if (*s == e) { 227 | if (--cont == 0) 228 | return s + 1; 229 | } else if (*s == b) 230 | cont++; 231 | } 232 | } 233 | 234 | /* string ends out of balance */ 235 | return (NULL); 236 | } 237 | 238 | static const char * 239 | max_expand(struct match_state *ms, const char *s, const char *p, const char *ep) 240 | { 241 | ptrdiff_t i = 0; 242 | /* counts maximum expand for item */ 243 | while (singlematch(ms, s + i, p, ep)) 244 | i++; 245 | /* keeps trying to match with the maximum repetitions */ 246 | while (i >= 0) { 247 | const char *res = match(ms, (s + i), ep + 1); 248 | if (res) 249 | return res; 250 | /* else didn't match; reduce 1 repetition to try again */ 251 | i--; 252 | } 253 | return NULL; 254 | } 255 | 256 | static const char * 257 | min_expand(struct match_state *ms, const char *s, const char *p, const char *ep) 258 | { 259 | for (;;) { 260 | const char *res = match(ms, s, ep + 1); 261 | if (res != NULL) 262 | return res; 263 | else if (singlematch(ms, s, p, ep)) 264 | s++; /* try with one more repetition */ 265 | else 266 | return NULL; 267 | } 268 | } 269 | 270 | static const char * 271 | start_capture(struct match_state *ms, const char *s, const char *p, int what) 272 | { 273 | const char *res; 274 | 275 | int level = ms->level; 276 | if (level >= ms->maxcaptures) { 277 | match_error(ms, "too many captures"); 278 | return (NULL); 279 | } 280 | ms->capture[level].init = s; 281 | ms->capture[level].len = what; 282 | ms->level = level + 1; 283 | /* undo capture if match failed */ 284 | if ((res = match(ms, s, p)) == NULL) 285 | ms->level--; 286 | return res; 287 | } 288 | 289 | static const char * 290 | end_capture(struct match_state *ms, const char *s, const char *p) 291 | { 292 | int l = capture_to_close(ms); 293 | const char *res; 294 | if (l == -1) 295 | return NULL; 296 | /* close capture */ 297 | ms->capture[l].len = s - ms->capture[l].init; 298 | /* undo capture if match failed */ 299 | if ((res = match(ms, s, p)) == NULL) 300 | ms->capture[l].len = CAP_UNFINISHED; 301 | return res; 302 | } 303 | 304 | static const char * 305 | match_capture(struct match_state *ms, const char *s, int l) 306 | { 307 | size_t len; 308 | l = check_capture(ms, l); 309 | if (l == -1) 310 | return NULL; 311 | len = ms->capture[l].len; 312 | if ((size_t) (ms->src_end - s) >= len && 313 | memcmp(ms->capture[l].init, s, len) == 0) 314 | return s + len; 315 | else 316 | return NULL; 317 | } 318 | 319 | static const char * 320 | match(struct match_state *ms, const char *s, const char *p) 321 | { 322 | const char *ep, *res; 323 | char previous; 324 | 325 | if (ms->matchdepth-- == 0) { 326 | match_error(ms, "pattern too complex"); 327 | return (NULL); 328 | } 329 | 330 | /* using goto's to optimize tail recursion */ 331 | init: 332 | /* end of pattern? */ 333 | if (p != ms->p_end) { 334 | switch (*p) { 335 | case '(': 336 | /* start capture */ 337 | if (*(p + 1) == ')') 338 | /* position capture? */ 339 | s = start_capture(ms, s, p + 2, CAP_POSITION); 340 | else 341 | s = start_capture(ms, s, p + 1, CAP_UNFINISHED); 342 | break; 343 | case ')': 344 | /* end capture */ 345 | s = end_capture(ms, s, p + 1); 346 | break; 347 | case '$': 348 | /* is the '$' the last char in pattern? */ 349 | if ((p + 1) != ms->p_end) { 350 | /* no; go to default */ 351 | goto dflt; 352 | } 353 | /* check end of string */ 354 | s = (s == ms->src_end) ? s : NULL; 355 | break; 356 | case L_ESC: 357 | /* escaped sequences not in the format class[*+?-]? */ 358 | switch (*(p + 1)) { 359 | case 'b': 360 | /* balanced string? */ 361 | s = matchbalance(ms, s, p + 2); 362 | if (s != NULL) { 363 | p += 4; 364 | /* return match(ms, s, p + 4); */ 365 | goto init; 366 | } /* else fail (s == NULL) */ 367 | break; 368 | case 'f': 369 | /* frontier? */ 370 | p += 2; 371 | if (*p != '[') { 372 | match_error(ms, "missing '['" 373 | " after '%f' in pattern"); 374 | break; 375 | } 376 | /* points to what is next */ 377 | ep = classend(ms, p); 378 | if (ms->error != NULL) 379 | break; 380 | previous = 381 | (s == ms->src_init) ? '\0' : *(s - 1); 382 | if (!matchbracketclass(uchar(previous), 383 | p, ep - 1) && 384 | matchbracketclass(uchar(*s), 385 | p, ep - 1)) { 386 | p = ep; 387 | /* return match(ms, s, ep); */ 388 | goto init; 389 | } 390 | /* match failed */ 391 | s = NULL; 392 | break; 393 | case '0': 394 | case '1': 395 | case '2': 396 | case '3': 397 | case '4': 398 | case '5': 399 | case '6': 400 | case '7': 401 | case '8': 402 | case '9': 403 | /* capture results (%0-%9)? */ 404 | s = match_capture(ms, s, uchar(*(p + 1))); 405 | if (s != NULL) { 406 | p += 2; 407 | /* return match(ms, s, p + 2) */ 408 | goto init; 409 | } 410 | break; 411 | default: 412 | goto dflt; 413 | } 414 | break; 415 | default: 416 | 417 | /* pattern class plus optional suffix */ 418 | dflt: 419 | /* points to optional suffix */ 420 | ep = classend(ms, p); 421 | if (ms->error != NULL) 422 | break; 423 | 424 | /* does not match at least once? */ 425 | if (!singlematch(ms, s, p, ep)) { 426 | if (ms->repetitioncounter-- == 0) { 427 | match_error(ms, "max repetition items"); 428 | s = NULL; /* fail */ 429 | /* accept empty? */ 430 | } else if 431 | (*ep == '*' || *ep == '?' || *ep == '-') { 432 | p = ep + 1; 433 | /* return match(ms, s, ep + 1); */ 434 | goto init; 435 | } else { 436 | /* '+' or no suffix */ 437 | s = NULL; /* fail */ 438 | } 439 | } else { 440 | /* matched once */ 441 | /* handle optional suffix */ 442 | switch (*ep) { 443 | case '?': 444 | /* optional */ 445 | if ((res = 446 | match(ms, s + 1, ep + 1)) != NULL) 447 | s = res; 448 | else { 449 | /* 450 | * else return 451 | * match(ms, s, ep + 1); 452 | */ 453 | p = ep + 1; 454 | goto init; 455 | } 456 | break; 457 | case '+': 458 | /* 1 or more repetitions */ 459 | s++; /* 1 match already done */ 460 | /* FALLTHROUGH */ 461 | case '*': 462 | /* 0 or more repetitions */ 463 | s = max_expand(ms, s, p, ep); 464 | break; 465 | case '-': 466 | /* 0 or more repetitions (minimum) */ 467 | s = min_expand(ms, s, p, ep); 468 | break; 469 | default: 470 | /* no suffix */ 471 | s++; 472 | p = ep; 473 | /* return match(ms, s + 1, ep); */ 474 | goto init; 475 | } 476 | } 477 | break; 478 | } 479 | } 480 | ms->matchdepth++; 481 | return s; 482 | } 483 | 484 | static const char * 485 | lmemfind(const char *s1, size_t l1, 486 | const char *s2, size_t l2) 487 | { 488 | const char *init; 489 | 490 | if (l2 == 0) { 491 | /* empty strings are everywhere */ 492 | return (s1); 493 | } else if (l2 > l1) { 494 | /* avoids a negative 'l1' */ 495 | return (NULL); 496 | } else { 497 | /* 498 | * to search for a '*s2' inside 's1' 499 | * - 1st char will be checked by 'memchr' 500 | * - 's2' cannot be found after that 501 | */ 502 | l2--; 503 | l1 = l1 - l2; 504 | while (l1 > 0 && 505 | (init = (const char *)memchr(s1, *s2, l1)) != NULL) { 506 | /* 1st char is already checked */ 507 | init++; 508 | if (memcmp(init, s2 + 1, l2) == 0) 509 | return init - 1; 510 | else { 511 | /* correct 'l1' and 's1' to try again */ 512 | l1 -= init - s1; 513 | s1 = init; 514 | } 515 | } 516 | /* not found */ 517 | return (NULL); 518 | } 519 | } 520 | 521 | static int 522 | push_onecapture(struct match_state *ms, int i, const char *s, 523 | const char *e, struct str_find *sm) 524 | { 525 | if (i >= ms->level) { 526 | if (i == 0 || ms->level == 0) { 527 | /* add whole match */ 528 | sm->sm_so = (off_t)(s - ms->src_init); 529 | sm->sm_eo = (off_t)(e - s) + sm->sm_so; 530 | } else 531 | return match_error(ms, "invalid capture index"); 532 | } else { 533 | ptrdiff_t l = ms->capture[i].len; 534 | if (l == CAP_UNFINISHED) 535 | return match_error(ms, "unfinished capture"); 536 | sm->sm_so = ms->capture[i].init - ms->src_init; 537 | sm->sm_eo = sm->sm_so + l; 538 | } 539 | sm->sm_eo = sm->sm_eo < sm->sm_so ? sm->sm_so : sm->sm_eo; 540 | return (0); 541 | } 542 | 543 | static int 544 | push_captures(struct match_state *ms, const char *s, const char *e, 545 | struct str_find *sm, size_t nsm) 546 | { 547 | unsigned int i; 548 | unsigned int nlevels = (ms->level <= 0 && s) ? 1 : ms->level; 549 | 550 | if (nlevels > nsm) 551 | nlevels = nsm; 552 | for (i = 0; i < nlevels; i++) 553 | if (push_onecapture(ms, i, s, e, sm + i) == -1) 554 | break; 555 | 556 | /* number of strings pushed */ 557 | return (nlevels); 558 | } 559 | 560 | /* check whether pattern has no special characters */ 561 | static int 562 | nospecials(const char *p, size_t l) 563 | { 564 | size_t upto = 0; 565 | 566 | do { 567 | if (strpbrk(p + upto, SPECIALS)) { 568 | /* pattern has a special character */ 569 | return 0; 570 | } 571 | /* may have more after \0 */ 572 | upto += strlen(p + upto) + 1; 573 | } while (upto <= l); 574 | 575 | /* no special chars found */ 576 | return (1); 577 | } 578 | 579 | static int 580 | str_find_aux(struct match_state *ms, const char *pattern, const char *string, 581 | struct str_find *sm, size_t nsm, off_t init) 582 | { 583 | size_t ls = strlen(string); 584 | size_t lp = strlen(pattern); 585 | const char *s = string; 586 | const char *p = pattern; 587 | const char *s1, *s2; 588 | int anchor, i; 589 | 590 | if (init < 0) 591 | init = 0; 592 | else if (init > (off_t)ls) 593 | return match_error(ms, "starting after string's end"); 594 | s1 = s + init; 595 | 596 | if (nospecials(p, lp)) { 597 | /* do a plain search */ 598 | s2 = lmemfind(s1, ls - (size_t)init, p, lp); 599 | if (s2 != NULL) { 600 | i = 0; 601 | sm[i].sm_so = 0; 602 | sm[i].sm_eo = ls; 603 | if (nsm > 1) { 604 | i++; 605 | sm[i].sm_so = s2 - s; 606 | sm[i].sm_eo = (s2 - s) + lp; 607 | } 608 | return (i + 1); 609 | } 610 | return (0); 611 | } 612 | 613 | anchor = (*p == '^'); 614 | if (anchor) { 615 | p++; 616 | lp--; /* skip anchor character */ 617 | } 618 | ms->maxcaptures = (nsm > MAXCAPTURES ? MAXCAPTURES : nsm) - 1; 619 | ms->matchdepth = MAXCCALLS; 620 | ms->repetitioncounter = MAXREPETITION; 621 | ms->src_init = s; 622 | ms->src_end = s + ls; 623 | ms->p_end = p + lp; 624 | do { 625 | const char *res; 626 | ms->level = 0; 627 | if ((res = match(ms, s1, p)) != NULL) { 628 | sm->sm_so = 0; 629 | sm->sm_eo = ls; 630 | return push_captures(ms, s1, res, sm + 1, nsm - 1) + 1; 631 | 632 | } else if (ms->error != NULL) { 633 | return 0; 634 | } 635 | } while (s1++ < ms->src_end && !anchor); 636 | 637 | return 0; 638 | } 639 | 640 | int 641 | str_find(const char *string, const char *pattern, struct str_find *sm, 642 | size_t nsm, const char **errstr) 643 | { 644 | struct match_state ms; 645 | int ret; 646 | 647 | memset(&ms, 0, sizeof(ms)); 648 | memset(sm, 0, nsm * sizeof(*sm)); 649 | 650 | ret = str_find_aux(&ms, pattern, string, sm, nsm, 0); 651 | if (ms.error != NULL) { 652 | /* Return 0 on error and store the error string */ 653 | *errstr = ms.error; 654 | ret = 0; 655 | } else 656 | *errstr = NULL; 657 | 658 | return (ret); 659 | } 660 | 661 | int 662 | str_match(const char *string, const char *pattern, struct str_match *m, 663 | const char **errstr) 664 | { 665 | struct str_find sm[MAXCAPTURES]; 666 | struct match_state ms; 667 | int ret, i; 668 | size_t len, nsm; 669 | 670 | nsm = MAXCAPTURES; 671 | memset(&ms, 0, sizeof(ms)); 672 | memset(sm, 0, sizeof(sm)); 673 | memset(m, 0, sizeof(*m)); 674 | 675 | ret = str_find_aux(&ms, pattern, string, sm, nsm, 0); 676 | if (ret <= 0 || ms.error != NULL) { 677 | /* Return -1 on error and store the error string */ 678 | *errstr = ms.error; 679 | return (-1); 680 | } 681 | 682 | if ((m->sm_match = calloc(ret, sizeof(char *))) == NULL) { 683 | *errstr = strerror(errno); 684 | return (-1); 685 | } 686 | m->sm_nmatch = ret; 687 | 688 | for (i = 0; i < ret; i++) { 689 | if (sm[i].sm_so > sm[i].sm_eo) 690 | continue; 691 | len = sm[i].sm_eo - sm[i].sm_so; 692 | if ((m->sm_match[i] = strndup(string + 693 | sm[i].sm_so, len)) == NULL) { 694 | *errstr = strerror(errno); 695 | str_match_free(m); 696 | return (-1); 697 | } 698 | } 699 | 700 | *errstr = NULL; 701 | return (0); 702 | } 703 | 704 | void 705 | str_match_free(struct str_match *m) 706 | { 707 | unsigned int i = 0; 708 | for (i = 0; i < m->sm_nmatch; i++) 709 | free(m->sm_match[i]); 710 | free(m->sm_match); 711 | m->sm_match = NULL; 712 | m->sm_nmatch = 0; 713 | } 714 | -------------------------------------------------------------------------------- /httpd/patterns.h: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: patterns.h,v 1.3 2015/12/12 19:59:43 mmcc Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 2015 Reyk Floeter 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #ifndef PATTERNS_H 20 | #define PATTERNS_H 21 | 22 | #include 23 | 24 | #define MAXCAPTURES 32 /* Max no. of allowed captures in pattern */ 25 | #define MAXCCALLS 200 /* Max recusion depth in pattern matching */ 26 | #define MAXREPETITION 0xfffff /* Max for repetition items */ 27 | 28 | struct str_find { 29 | off_t sm_so; /* start offset of match */ 30 | off_t sm_eo; /* end offset of match */ 31 | }; 32 | 33 | struct str_match { 34 | char **sm_match; /* allocated array of matched strings */ 35 | unsigned int sm_nmatch; /* number of elements in array */ 36 | }; 37 | 38 | __BEGIN_DECLS 39 | int str_find(const char *, const char *, struct str_find *, size_t, 40 | const char **); 41 | int str_match(const char *, const char *, struct str_match *, 42 | const char **); 43 | void str_match_free(struct str_match *); 44 | __END_DECLS 45 | 46 | #endif /* PATTERNS_H */ 47 | -------------------------------------------------------------------------------- /httpd/proc.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: proc.c,v 1.37 2017/05/28 10:37:26 benno Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 2010 - 2016 Reyk Floeter 5 | * Copyright (c) 2008 Pierre-Yves Ritschard 6 | * 7 | * Permission to use, copy, modify, and distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include "httpd.h" 37 | 38 | void proc_exec(struct privsep *, struct privsep_proc *, unsigned int, 39 | int, char **); 40 | void proc_setup(struct privsep *, struct privsep_proc *, unsigned int); 41 | void proc_open(struct privsep *, int, int); 42 | void proc_accept(struct privsep *, int, enum privsep_procid, 43 | unsigned int); 44 | void proc_close(struct privsep *); 45 | int proc_ispeer(struct privsep_proc *, unsigned int, enum privsep_procid); 46 | void proc_shutdown(struct privsep_proc *); 47 | void proc_sig_handler(int, short, void *); 48 | void proc_range(struct privsep *, enum privsep_procid, int *, int *); 49 | int proc_dispatch_null(int, struct privsep_proc *, struct imsg *); 50 | 51 | int 52 | proc_ispeer(struct privsep_proc *procs, unsigned int nproc, 53 | enum privsep_procid type) 54 | { 55 | unsigned int i; 56 | 57 | for (i = 0; i < nproc; i++) 58 | if (procs[i].p_id == type) 59 | return (1); 60 | return (0); 61 | } 62 | 63 | enum privsep_procid 64 | proc_getid(struct privsep_proc *procs, unsigned int nproc, 65 | const char *proc_name) 66 | { 67 | struct privsep_proc *p; 68 | unsigned int proc; 69 | 70 | for (proc = 0; proc < nproc; proc++) { 71 | p = &procs[proc]; 72 | if (strcmp(p->p_title, proc_name)) 73 | continue; 74 | 75 | return (p->p_id); 76 | } 77 | 78 | return (PROC_MAX); 79 | } 80 | 81 | void 82 | proc_exec(struct privsep *ps, struct privsep_proc *procs, unsigned int nproc, 83 | int argc, char **argv) 84 | { 85 | unsigned int proc, nargc, i, proc_i; 86 | char **nargv; 87 | struct privsep_proc *p; 88 | char num[32]; 89 | int fd; 90 | 91 | /* Prepare the new process argv. */ 92 | nargv = calloc(argc + 5, sizeof(char *)); 93 | if (nargv == NULL) 94 | fatal("%s: calloc", __func__); 95 | 96 | /* Copy call argument first. */ 97 | nargc = 0; 98 | nargv[nargc++] = argv[0]; 99 | 100 | /* Set process name argument and save the position. */ 101 | nargv[nargc++] = "-P"; 102 | proc_i = nargc; 103 | nargc++; 104 | 105 | /* Point process instance arg to stack and copy the original args. */ 106 | nargv[nargc++] = "-I"; 107 | nargv[nargc++] = num; 108 | for (i = 1; i < (unsigned int) argc; i++) 109 | nargv[nargc++] = argv[i]; 110 | 111 | nargv[nargc] = NULL; 112 | 113 | for (proc = 0; proc < nproc; proc++) { 114 | p = &procs[proc]; 115 | 116 | /* Update args with process title. */ 117 | nargv[proc_i] = (char *)(uintptr_t)p->p_title; 118 | 119 | /* Fire children processes. */ 120 | for (i = 0; i < ps->ps_instances[p->p_id]; i++) { 121 | /* Update the process instance number. */ 122 | snprintf(num, sizeof(num), "%u", i); 123 | 124 | fd = ps->ps_pipes[p->p_id][i].pp_pipes[PROC_PARENT][0]; 125 | ps->ps_pipes[p->p_id][i].pp_pipes[PROC_PARENT][0] = -1; 126 | 127 | switch (fork()) { 128 | case -1: 129 | fatal("%s: fork", __func__); 130 | break; 131 | case 0: 132 | /* First create a new session */ 133 | if (setsid() == -1) 134 | fatal("setsid"); 135 | 136 | /* Prepare parent socket. */ 137 | if (fd != PROC_PARENT_SOCK_FILENO) { 138 | if (dup2(fd, PROC_PARENT_SOCK_FILENO) 139 | == -1) 140 | fatal("dup2"); 141 | } else if (fcntl(fd, F_SETFD, 0) == -1) 142 | fatal("fcntl"); 143 | 144 | execvp(argv[0], nargv); 145 | fatal("%s: execvp", __func__); 146 | break; 147 | default: 148 | /* Close child end. */ 149 | close(fd); 150 | break; 151 | } 152 | } 153 | } 154 | free(nargv); 155 | } 156 | 157 | void 158 | proc_connect(struct privsep *ps) 159 | { 160 | struct imsgev *iev; 161 | unsigned int src, dst, inst; 162 | 163 | /* Don't distribute any sockets if we are not really going to run. */ 164 | if (ps->ps_noaction) 165 | return; 166 | 167 | for (dst = 0; dst < PROC_MAX; dst++) { 168 | /* We don't communicate with ourselves. */ 169 | if (dst == PROC_PARENT) 170 | continue; 171 | 172 | for (inst = 0; inst < ps->ps_instances[dst]; inst++) { 173 | iev = &ps->ps_ievs[dst][inst]; 174 | imsg_init(&iev->ibuf, ps->ps_pp->pp_pipes[dst][inst]); 175 | event_set(&iev->ev, iev->ibuf.fd, iev->events, 176 | iev->handler, iev->data); 177 | event_add(&iev->ev, NULL); 178 | } 179 | } 180 | 181 | /* Distribute the socketpair()s for everyone. */ 182 | for (src = 0; src < PROC_MAX; src++) 183 | for (dst = src; dst < PROC_MAX; dst++) { 184 | /* Parent already distributed its fds. */ 185 | if (src == PROC_PARENT || dst == PROC_PARENT) 186 | continue; 187 | 188 | proc_open(ps, src, dst); 189 | } 190 | } 191 | 192 | void 193 | proc_init(struct privsep *ps, struct privsep_proc *procs, unsigned int nproc, 194 | int argc, char **argv, enum privsep_procid proc_id) 195 | { 196 | struct privsep_proc *p = NULL; 197 | struct privsep_pipes *pa, *pb; 198 | unsigned int proc; 199 | unsigned int dst; 200 | int fds[2]; 201 | 202 | /* Don't initiate anything if we are not really going to run. */ 203 | if (ps->ps_noaction) 204 | return; 205 | 206 | if (proc_id == PROC_PARENT) { 207 | privsep_process = PROC_PARENT; 208 | proc_setup(ps, procs, nproc); 209 | 210 | /* 211 | * Create the children sockets so we can use them 212 | * to distribute the rest of the socketpair()s using 213 | * proc_connect() later. 214 | */ 215 | for (dst = 0; dst < PROC_MAX; dst++) { 216 | /* Don't create socket for ourselves. */ 217 | if (dst == PROC_PARENT) 218 | continue; 219 | 220 | for (proc = 0; proc < ps->ps_instances[dst]; proc++) { 221 | pa = &ps->ps_pipes[PROC_PARENT][0]; 222 | pb = &ps->ps_pipes[dst][proc]; 223 | if (socketpair(AF_UNIX, 224 | SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 225 | PF_UNSPEC, fds) == -1) 226 | fatal("%s: socketpair", __func__); 227 | 228 | pa->pp_pipes[dst][proc] = fds[0]; 229 | pb->pp_pipes[PROC_PARENT][0] = fds[1]; 230 | } 231 | } 232 | 233 | /* Engage! */ 234 | proc_exec(ps, procs, nproc, argc, argv); 235 | return; 236 | } 237 | 238 | /* Initialize a child */ 239 | for (proc = 0; proc < nproc; proc++) { 240 | if (procs[proc].p_id != proc_id) 241 | continue; 242 | p = &procs[proc]; 243 | break; 244 | } 245 | if (p == NULL || p->p_init == NULL) 246 | fatalx("%s: process %d missing process initialization", 247 | __func__, proc_id); 248 | 249 | p->p_init(ps, p); 250 | 251 | fatalx("failed to initiate child process"); 252 | } 253 | 254 | void 255 | proc_accept(struct privsep *ps, int fd, enum privsep_procid dst, 256 | unsigned int n) 257 | { 258 | struct privsep_pipes *pp = ps->ps_pp; 259 | struct imsgev *iev; 260 | 261 | if (ps->ps_ievs[dst] == NULL) { 262 | #if DEBUG > 1 263 | log_debug("%s: %s src %d %d to dst %d %d not connected", 264 | __func__, ps->ps_title[privsep_process], 265 | privsep_process, ps->ps_instance + 1, 266 | dst, n + 1); 267 | #endif 268 | close(fd); 269 | return; 270 | } 271 | 272 | if (pp->pp_pipes[dst][n] != -1) { 273 | log_warnx("%s: duplicated descriptor", __func__); 274 | close(fd); 275 | return; 276 | } else 277 | pp->pp_pipes[dst][n] = fd; 278 | 279 | iev = &ps->ps_ievs[dst][n]; 280 | imsg_init(&iev->ibuf, fd); 281 | event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev->data); 282 | event_add(&iev->ev, NULL); 283 | } 284 | 285 | void 286 | proc_setup(struct privsep *ps, struct privsep_proc *procs, unsigned int nproc) 287 | { 288 | unsigned int i, j, src, dst, id; 289 | struct privsep_pipes *pp; 290 | 291 | /* Initialize parent title, ps_instances and procs. */ 292 | ps->ps_title[PROC_PARENT] = "parent"; 293 | 294 | for (src = 0; src < PROC_MAX; src++) 295 | /* Default to 1 process instance */ 296 | if (ps->ps_instances[src] < 1) 297 | ps->ps_instances[src] = 1; 298 | 299 | for (src = 0; src < nproc; src++) { 300 | procs[src].p_ps = ps; 301 | if (procs[src].p_cb == NULL) 302 | procs[src].p_cb = proc_dispatch_null; 303 | 304 | id = procs[src].p_id; 305 | ps->ps_title[id] = procs[src].p_title; 306 | if ((ps->ps_ievs[id] = calloc(ps->ps_instances[id], 307 | sizeof(struct imsgev))) == NULL) 308 | fatal("%s: calloc", __func__); 309 | 310 | /* With this set up, we are ready to call imsg_init(). */ 311 | for (i = 0; i < ps->ps_instances[id]; i++) { 312 | ps->ps_ievs[id][i].handler = proc_dispatch; 313 | ps->ps_ievs[id][i].events = EV_READ; 314 | ps->ps_ievs[id][i].proc = &procs[src]; 315 | ps->ps_ievs[id][i].data = &ps->ps_ievs[id][i]; 316 | } 317 | } 318 | 319 | /* 320 | * Allocate pipes for all process instances (incl. parent) 321 | * 322 | * - ps->ps_pipes: N:M mapping 323 | * N source processes connected to M destination processes: 324 | * [src][instances][dst][instances], for example 325 | * [PROC_RELAY][3][PROC_CA][3] 326 | * 327 | * - ps->ps_pp: per-process 1:M part of ps->ps_pipes 328 | * Each process instance has a destination array of socketpair fds: 329 | * [dst][instances], for example 330 | * [PROC_PARENT][0] 331 | */ 332 | for (src = 0; src < PROC_MAX; src++) { 333 | /* Allocate destination array for each process */ 334 | if ((ps->ps_pipes[src] = calloc(ps->ps_instances[src], 335 | sizeof(struct privsep_pipes))) == NULL) 336 | fatal("%s: calloc", __func__); 337 | 338 | for (i = 0; i < ps->ps_instances[src]; i++) { 339 | pp = &ps->ps_pipes[src][i]; 340 | 341 | for (dst = 0; dst < PROC_MAX; dst++) { 342 | /* Allocate maximum fd integers */ 343 | if ((pp->pp_pipes[dst] = 344 | calloc(ps->ps_instances[dst], 345 | sizeof(int))) == NULL) 346 | fatal("%s: calloc", __func__); 347 | 348 | /* Mark fd as unused */ 349 | for (j = 0; j < ps->ps_instances[dst]; j++) 350 | pp->pp_pipes[dst][j] = -1; 351 | } 352 | } 353 | } 354 | 355 | ps->ps_pp = &ps->ps_pipes[privsep_process][ps->ps_instance]; 356 | } 357 | 358 | void 359 | proc_kill(struct privsep *ps) 360 | { 361 | char *cause; 362 | pid_t pid; 363 | int len, status; 364 | 365 | if (privsep_process != PROC_PARENT) 366 | return; 367 | 368 | proc_close(ps); 369 | 370 | do { 371 | pid = waitpid(WAIT_ANY, &status, 0); 372 | if (pid <= 0) 373 | continue; 374 | 375 | if (WIFSIGNALED(status)) { 376 | len = asprintf(&cause, "terminated; signal %d", 377 | WTERMSIG(status)); 378 | } else if (WIFEXITED(status)) { 379 | if (WEXITSTATUS(status) != 0) 380 | len = asprintf(&cause, "exited abnormally"); 381 | else 382 | len = 0; 383 | } else 384 | len = -1; 385 | 386 | if (len == 0) { 387 | /* child exited OK, don't print a warning message */ 388 | } else if (len != -1) { 389 | log_warnx("lost child: pid %u %s", pid, cause); 390 | free(cause); 391 | } else 392 | log_warnx("lost child: pid %u", pid); 393 | } while (pid != -1 || (pid == -1 && errno == EINTR)); 394 | } 395 | 396 | void 397 | proc_open(struct privsep *ps, int src, int dst) 398 | { 399 | struct privsep_pipes *pa, *pb; 400 | struct privsep_fd pf; 401 | int fds[2]; 402 | unsigned int i, j; 403 | 404 | /* Exchange pipes between process. */ 405 | for (i = 0; i < ps->ps_instances[src]; i++) { 406 | for (j = 0; j < ps->ps_instances[dst]; j++) { 407 | /* Don't create sockets for ourself. */ 408 | if (src == dst && i == j) 409 | continue; 410 | 411 | pa = &ps->ps_pipes[src][i]; 412 | pb = &ps->ps_pipes[dst][j]; 413 | if (socketpair(AF_UNIX, 414 | SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 415 | PF_UNSPEC, fds) == -1) 416 | fatal("%s: socketpair", __func__); 417 | 418 | pa->pp_pipes[dst][j] = fds[0]; 419 | pb->pp_pipes[src][i] = fds[1]; 420 | 421 | pf.pf_procid = src; 422 | pf.pf_instance = i; 423 | if (proc_compose_imsg(ps, dst, j, IMSG_CTL_PROCFD, 424 | -1, pb->pp_pipes[src][i], &pf, sizeof(pf)) == -1) 425 | fatal("%s: proc_compose_imsg", __func__); 426 | 427 | pf.pf_procid = dst; 428 | pf.pf_instance = j; 429 | if (proc_compose_imsg(ps, src, i, IMSG_CTL_PROCFD, 430 | -1, pa->pp_pipes[dst][j], &pf, sizeof(pf)) == -1) 431 | fatal("%s: proc_compose_imsg", __func__); 432 | 433 | /* 434 | * We have to flush to send the descriptors and close 435 | * them to avoid the fd ramp on startup. 436 | */ 437 | if (proc_flush_imsg(ps, src, i) == -1 || 438 | proc_flush_imsg(ps, dst, j) == -1) 439 | fatal("%s: imsg_flush", __func__); 440 | } 441 | } 442 | } 443 | 444 | void 445 | proc_close(struct privsep *ps) 446 | { 447 | unsigned int dst, n; 448 | struct privsep_pipes *pp; 449 | 450 | if (ps == NULL) 451 | return; 452 | 453 | pp = ps->ps_pp; 454 | 455 | for (dst = 0; dst < PROC_MAX; dst++) { 456 | if (ps->ps_ievs[dst] == NULL) 457 | continue; 458 | 459 | for (n = 0; n < ps->ps_instances[dst]; n++) { 460 | if (pp->pp_pipes[dst][n] == -1) 461 | continue; 462 | 463 | /* Cancel the fd, close and invalidate the fd */ 464 | event_del(&(ps->ps_ievs[dst][n].ev)); 465 | imsg_clear(&(ps->ps_ievs[dst][n].ibuf)); 466 | close(pp->pp_pipes[dst][n]); 467 | pp->pp_pipes[dst][n] = -1; 468 | } 469 | free(ps->ps_ievs[dst]); 470 | } 471 | } 472 | 473 | void 474 | proc_shutdown(struct privsep_proc *p) 475 | { 476 | struct privsep *ps = p->p_ps; 477 | 478 | if (p->p_id == PROC_CONTROL && ps) 479 | control_cleanup(&ps->ps_csock); 480 | 481 | if (p->p_shutdown != NULL) 482 | (*p->p_shutdown)(); 483 | 484 | proc_close(ps); 485 | 486 | log_info("%s exiting, pid %d", p->p_title, getpid()); 487 | 488 | exit(0); 489 | } 490 | 491 | void 492 | proc_sig_handler(int sig, short event, void *arg) 493 | { 494 | struct privsep_proc *p = arg; 495 | 496 | switch (sig) { 497 | case SIGINT: 498 | case SIGTERM: 499 | proc_shutdown(p); 500 | break; 501 | case SIGCHLD: 502 | case SIGHUP: 503 | case SIGPIPE: 504 | case SIGUSR1: 505 | /* ignore */ 506 | break; 507 | default: 508 | fatalx("%s: unexpected signal", __func__); 509 | /* NOTREACHED */ 510 | } 511 | } 512 | 513 | void 514 | proc_run(struct privsep *ps, struct privsep_proc *p, 515 | struct privsep_proc *procs, unsigned int nproc, 516 | void (*run)(struct privsep *, struct privsep_proc *, void *), void *arg) 517 | { 518 | struct passwd *pw; 519 | const char *root; 520 | struct control_sock *rcs; 521 | 522 | log_procinit(p->p_title); 523 | 524 | /* Set the process group of the current process */ 525 | setpgid(0, 0); 526 | 527 | if (p->p_id == PROC_CONTROL && ps->ps_instance == 0) { 528 | if (control_init(ps, &ps->ps_csock) == -1) 529 | fatalx("%s: control_init", __func__); 530 | TAILQ_FOREACH(rcs, &ps->ps_rcsocks, cs_entry) 531 | if (control_init(ps, rcs) == -1) 532 | fatalx("%s: control_init", __func__); 533 | } 534 | 535 | /* Use non-standard user */ 536 | if (p->p_pw != NULL) 537 | pw = p->p_pw; 538 | else 539 | pw = ps->ps_pw; 540 | 541 | /* Change root directory */ 542 | if (p->p_chroot != NULL) 543 | root = p->p_chroot; 544 | else 545 | root = pw->pw_dir; 546 | 547 | if (chroot(root) == -1) 548 | fatal("%s: chroot", __func__); 549 | if (chdir("/") == -1) 550 | fatal("%s: chdir(\"/\")", __func__); 551 | 552 | privsep_process = p->p_id; 553 | 554 | setproctitle("%s", p->p_title); 555 | 556 | if (setgroups(1, &pw->pw_gid) || 557 | setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || 558 | setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) 559 | fatal("%s: cannot drop privileges", __func__); 560 | 561 | event_init(); 562 | 563 | signal_set(&ps->ps_evsigint, SIGINT, proc_sig_handler, p); 564 | signal_set(&ps->ps_evsigterm, SIGTERM, proc_sig_handler, p); 565 | signal_set(&ps->ps_evsigchld, SIGCHLD, proc_sig_handler, p); 566 | signal_set(&ps->ps_evsighup, SIGHUP, proc_sig_handler, p); 567 | signal_set(&ps->ps_evsigpipe, SIGPIPE, proc_sig_handler, p); 568 | signal_set(&ps->ps_evsigusr1, SIGUSR1, proc_sig_handler, p); 569 | 570 | signal_add(&ps->ps_evsigint, NULL); 571 | signal_add(&ps->ps_evsigterm, NULL); 572 | signal_add(&ps->ps_evsigchld, NULL); 573 | signal_add(&ps->ps_evsighup, NULL); 574 | signal_add(&ps->ps_evsigpipe, NULL); 575 | signal_add(&ps->ps_evsigusr1, NULL); 576 | 577 | proc_setup(ps, procs, nproc); 578 | proc_accept(ps, PROC_PARENT_SOCK_FILENO, PROC_PARENT, 0); 579 | if (p->p_id == PROC_CONTROL && ps->ps_instance == 0) { 580 | TAILQ_INIT(&ctl_conns); 581 | if (control_listen(&ps->ps_csock) == -1) 582 | fatalx("%s: control_listen", __func__); 583 | TAILQ_FOREACH(rcs, &ps->ps_rcsocks, cs_entry) 584 | if (control_listen(rcs) == -1) 585 | fatalx("%s: control_listen", __func__); 586 | } 587 | 588 | DPRINTF("%s: %s %d/%d, pid %d", __func__, p->p_title, 589 | ps->ps_instance + 1, ps->ps_instances[p->p_id], getpid()); 590 | 591 | if (run != NULL) 592 | run(ps, p, arg); 593 | 594 | event_dispatch(); 595 | 596 | proc_shutdown(p); 597 | } 598 | 599 | void 600 | proc_dispatch(int fd, short event, void *arg) 601 | { 602 | struct imsgev *iev = arg; 603 | struct privsep_proc *p = iev->proc; 604 | struct privsep *ps = p->p_ps; 605 | struct imsgbuf *ibuf; 606 | struct imsg imsg; 607 | ssize_t n; 608 | int verbose; 609 | const char *title; 610 | struct privsep_fd pf; 611 | 612 | title = ps->ps_title[privsep_process]; 613 | ibuf = &iev->ibuf; 614 | 615 | if (event & EV_READ) { 616 | if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) 617 | fatal("%s: imsg_read", __func__); 618 | if (n == 0) { 619 | /* this pipe is dead, so remove the event handler */ 620 | event_del(&iev->ev); 621 | event_loopexit(NULL); 622 | return; 623 | } 624 | } 625 | 626 | if (event & EV_WRITE) { 627 | if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) 628 | fatal("%s: msgbuf_write", __func__); 629 | if (n == 0) { 630 | /* this pipe is dead, so remove the event handler */ 631 | event_del(&iev->ev); 632 | event_loopexit(NULL); 633 | return; 634 | } 635 | } 636 | 637 | for (;;) { 638 | if ((n = imsg_get(ibuf, &imsg)) == -1) 639 | fatal("%s: imsg_get", __func__); 640 | if (n == 0) 641 | break; 642 | 643 | #if DEBUG > 1 644 | log_debug("%s: %s %d got imsg %d peerid %d from %s %d", 645 | __func__, title, ps->ps_instance + 1, 646 | imsg.hdr.type, imsg.hdr.peerid, p->p_title, imsg.hdr.pid); 647 | #endif 648 | 649 | /* 650 | * Check the message with the program callback 651 | */ 652 | if ((p->p_cb)(fd, p, &imsg) == 0) { 653 | /* Message was handled by the callback, continue */ 654 | imsg_free(&imsg); 655 | continue; 656 | } 657 | 658 | /* 659 | * Generic message handling 660 | */ 661 | switch (imsg.hdr.type) { 662 | case IMSG_CTL_VERBOSE: 663 | IMSG_SIZE_CHECK(&imsg, &verbose); 664 | memcpy(&verbose, imsg.data, sizeof(verbose)); 665 | log_setverbose(verbose); 666 | break; 667 | case IMSG_CTL_PROCFD: 668 | IMSG_SIZE_CHECK(&imsg, &pf); 669 | memcpy(&pf, imsg.data, sizeof(pf)); 670 | proc_accept(ps, imsg.fd, pf.pf_procid, 671 | pf.pf_instance); 672 | break; 673 | default: 674 | fatalx("%s: %s %d got invalid imsg %d peerid %d " 675 | "from %s %d", 676 | __func__, title, ps->ps_instance + 1, 677 | imsg.hdr.type, imsg.hdr.peerid, 678 | p->p_title, imsg.hdr.pid); 679 | } 680 | imsg_free(&imsg); 681 | } 682 | imsg_event_add(iev); 683 | } 684 | 685 | int 686 | proc_dispatch_null(int fd, struct privsep_proc *p, struct imsg *imsg) 687 | { 688 | return (-1); 689 | } 690 | 691 | /* 692 | * imsg helper functions 693 | */ 694 | 695 | void 696 | imsg_event_add(struct imsgev *iev) 697 | { 698 | if (iev->handler == NULL) { 699 | imsg_flush(&iev->ibuf); 700 | return; 701 | } 702 | 703 | iev->events = EV_READ; 704 | if (iev->ibuf.w.queued) 705 | iev->events |= EV_WRITE; 706 | 707 | event_del(&iev->ev); 708 | event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev->data); 709 | event_add(&iev->ev, NULL); 710 | } 711 | 712 | int 713 | imsg_compose_event(struct imsgev *iev, uint16_t type, uint32_t peerid, 714 | pid_t pid, int fd, void *data, uint16_t datalen) 715 | { 716 | int ret; 717 | 718 | if ((ret = imsg_compose(&iev->ibuf, type, peerid, 719 | pid, fd, data, datalen)) == -1) 720 | return (ret); 721 | imsg_event_add(iev); 722 | return (ret); 723 | } 724 | 725 | int 726 | imsg_composev_event(struct imsgev *iev, uint16_t type, uint32_t peerid, 727 | pid_t pid, int fd, const struct iovec *iov, int iovcnt) 728 | { 729 | int ret; 730 | 731 | if ((ret = imsg_composev(&iev->ibuf, type, peerid, 732 | pid, fd, iov, iovcnt)) == -1) 733 | return (ret); 734 | imsg_event_add(iev); 735 | return (ret); 736 | } 737 | 738 | void 739 | proc_range(struct privsep *ps, enum privsep_procid id, int *n, int *m) 740 | { 741 | if (*n == -1) { 742 | /* Use a range of all target instances */ 743 | *n = 0; 744 | *m = ps->ps_instances[id]; 745 | } else { 746 | /* Use only a single slot of the specified peer process */ 747 | *m = *n + 1; 748 | } 749 | } 750 | 751 | int 752 | proc_compose_imsg(struct privsep *ps, enum privsep_procid id, int n, 753 | uint16_t type, uint32_t peerid, int fd, void *data, uint16_t datalen) 754 | { 755 | int m; 756 | 757 | proc_range(ps, id, &n, &m); 758 | for (; n < m; n++) { 759 | if (imsg_compose_event(&ps->ps_ievs[id][n], 760 | type, peerid, ps->ps_instance + 1, fd, data, datalen) == -1) 761 | return (-1); 762 | } 763 | 764 | return (0); 765 | } 766 | 767 | int 768 | proc_compose(struct privsep *ps, enum privsep_procid id, 769 | uint16_t type, void *data, uint16_t datalen) 770 | { 771 | return (proc_compose_imsg(ps, id, -1, type, -1, -1, data, datalen)); 772 | } 773 | 774 | int 775 | proc_composev_imsg(struct privsep *ps, enum privsep_procid id, int n, 776 | uint16_t type, uint32_t peerid, int fd, const struct iovec *iov, int iovcnt) 777 | { 778 | int m; 779 | 780 | proc_range(ps, id, &n, &m); 781 | for (; n < m; n++) 782 | if (imsg_composev_event(&ps->ps_ievs[id][n], 783 | type, peerid, ps->ps_instance + 1, fd, iov, iovcnt) == -1) 784 | return (-1); 785 | 786 | return (0); 787 | } 788 | 789 | int 790 | proc_composev(struct privsep *ps, enum privsep_procid id, 791 | uint16_t type, const struct iovec *iov, int iovcnt) 792 | { 793 | return (proc_composev_imsg(ps, id, -1, type, -1, -1, iov, iovcnt)); 794 | } 795 | 796 | int 797 | proc_forward_imsg(struct privsep *ps, struct imsg *imsg, 798 | enum privsep_procid id, int n) 799 | { 800 | return (proc_compose_imsg(ps, id, n, imsg->hdr.type, 801 | imsg->hdr.peerid, imsg->fd, imsg->data, IMSG_DATA_SIZE(imsg))); 802 | } 803 | 804 | struct imsgbuf * 805 | proc_ibuf(struct privsep *ps, enum privsep_procid id, int n) 806 | { 807 | int m; 808 | 809 | proc_range(ps, id, &n, &m); 810 | return (&ps->ps_ievs[id][n].ibuf); 811 | } 812 | 813 | struct imsgev * 814 | proc_iev(struct privsep *ps, enum privsep_procid id, int n) 815 | { 816 | int m; 817 | 818 | proc_range(ps, id, &n, &m); 819 | return (&ps->ps_ievs[id][n]); 820 | } 821 | 822 | /* This function should only be called with care as it breaks async I/O */ 823 | int 824 | proc_flush_imsg(struct privsep *ps, enum privsep_procid id, int n) 825 | { 826 | struct imsgbuf *ibuf; 827 | int m, ret = 0; 828 | 829 | proc_range(ps, id, &n, &m); 830 | for (; n < m; n++) { 831 | if ((ibuf = proc_ibuf(ps, id, n)) == NULL) 832 | return (-1); 833 | do { 834 | ret = imsg_flush(ibuf); 835 | } while (ret == -1 && errno == EAGAIN); 836 | if (ret == -1) 837 | break; 838 | imsg_event_add(&ps->ps_ievs[id][n]); 839 | } 840 | 841 | return (ret); 842 | } 843 | -------------------------------------------------------------------------------- /httpd/server_file.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: server_file.c,v 1.65 2017/02/02 22:19:59 reyk Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 2006 - 2017 Reyk Floeter 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "httpd.h" 35 | #include "http.h" 36 | 37 | #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) 38 | #define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b)) 39 | 40 | int server_file_access(struct httpd *, struct client *, 41 | char *, size_t); 42 | int server_file_request(struct httpd *, struct client *, 43 | char *, struct stat *); 44 | int server_partial_file_request(struct httpd *, struct client *, 45 | char *, struct stat *, char *); 46 | int server_file_index(struct httpd *, struct client *, 47 | struct stat *); 48 | int server_file_modified_since(struct http_descriptor *, 49 | struct stat *); 50 | int server_file_method(struct client *); 51 | int parse_range_spec(char *, size_t, struct range *); 52 | int parse_ranges(struct client *, char *, size_t); 53 | 54 | int 55 | server_file_access(struct httpd *env, struct client *clt, 56 | char *path, size_t len) 57 | { 58 | struct http_descriptor *desc = clt->clt_descreq; 59 | struct server_config *srv_conf = clt->clt_srv_conf; 60 | struct stat st; 61 | struct kv *r, key; 62 | char *newpath, *encodedpath; 63 | int ret; 64 | 65 | errno = 0; 66 | 67 | if (access(path, R_OK) == -1) { 68 | goto fail; 69 | } else if (stat(path, &st) == -1) { 70 | goto fail; 71 | } else if (S_ISDIR(st.st_mode)) { 72 | /* Deny access if directory indexing is disabled */ 73 | if (srv_conf->flags & SRVFLAG_NO_INDEX) { 74 | errno = EACCES; 75 | goto fail; 76 | } 77 | 78 | if (desc->http_path_alias != NULL) { 79 | /* Recursion - the index "file" is a directory? */ 80 | errno = EINVAL; 81 | goto fail; 82 | } 83 | 84 | /* Redirect to path with trailing "/" */ 85 | if (path[strlen(path) - 1] != '/') { 86 | if ((encodedpath = url_encode(desc->http_path)) == NULL) 87 | return (500); 88 | if (asprintf(&newpath, "http%s://%s%s/", 89 | srv_conf->flags & SRVFLAG_TLS ? "s" : "", 90 | desc->http_host, encodedpath) == -1) { 91 | free(encodedpath); 92 | return (500); 93 | } 94 | free(encodedpath); 95 | 96 | /* Path alias will be used for the redirection */ 97 | desc->http_path_alias = newpath; 98 | 99 | /* Indicate that the file has been moved */ 100 | return (301); 101 | } 102 | 103 | /* Append the default index file to the location */ 104 | if (asprintf(&newpath, "%s%s", desc->http_path, 105 | srv_conf->index) == -1) 106 | return (500); 107 | desc->http_path_alias = newpath; 108 | if (server_getlocation(clt, newpath) != srv_conf) { 109 | /* The location has changed */ 110 | return (server_file(env, clt)); 111 | } 112 | 113 | /* Otherwise append the default index file to the path */ 114 | if (strlcat(path, srv_conf->index, len) >= len) { 115 | errno = EACCES; 116 | goto fail; 117 | } 118 | 119 | ret = server_file_access(env, clt, path, len); 120 | if (ret == 404) { 121 | /* 122 | * Index file not found; fail if auto-indexing is 123 | * not enabled, otherwise return success but 124 | * indicate directory with S_ISDIR of the previous 125 | * stat. 126 | */ 127 | if ((srv_conf->flags & SRVFLAG_AUTO_INDEX) == 0) { 128 | errno = EACCES; 129 | goto fail; 130 | } 131 | 132 | return (server_file_index(env, clt, &st)); 133 | } 134 | return (ret); 135 | } else if (!S_ISREG(st.st_mode)) { 136 | /* Don't follow symlinks and ignore special files */ 137 | errno = EACCES; 138 | goto fail; 139 | } 140 | 141 | key.kv_key = "Range"; 142 | r = kv_find(&desc->http_headers, &key); 143 | if (r != NULL) 144 | return (server_partial_file_request(env, clt, path, &st, 145 | r->kv_value)); 146 | else 147 | return (server_file_request(env, clt, path, &st)); 148 | 149 | fail: 150 | switch (errno) { 151 | case ENOENT: 152 | case ENOTDIR: 153 | return (404); 154 | case EACCES: 155 | return (403); 156 | default: 157 | return (500); 158 | } 159 | 160 | /* NOTREACHED */ 161 | } 162 | 163 | int 164 | server_file(struct httpd *env, struct client *clt) 165 | { 166 | struct http_descriptor *desc = clt->clt_descreq; 167 | struct server_config *srv_conf = clt->clt_srv_conf; 168 | char path[PATH_MAX]; 169 | const char *stripped, *errstr = NULL; 170 | int ret = 500; 171 | 172 | if (srv_conf->flags & SRVFLAG_FCGI) 173 | return (server_fcgi(env, clt)); 174 | 175 | /* Request path is already canonicalized */ 176 | stripped = server_root_strip( 177 | desc->http_path_alias != NULL ? 178 | desc->http_path_alias : desc->http_path, 179 | srv_conf->strip); 180 | if ((size_t)snprintf(path, sizeof(path), "%s%s", 181 | srv_conf->root, stripped) >= sizeof(path)) { 182 | errstr = desc->http_path; 183 | goto abort; 184 | } 185 | 186 | /* Returns HTTP status code on error */ 187 | if ((ret = server_file_access(env, clt, path, sizeof(path))) > 0) { 188 | errstr = desc->http_path_alias != NULL ? 189 | desc->http_path_alias : desc->http_path; 190 | goto abort; 191 | } 192 | 193 | return (ret); 194 | 195 | abort: 196 | if (errstr == NULL) 197 | errstr = strerror(errno); 198 | server_abort_http(clt, ret, errstr); 199 | return (-1); 200 | } 201 | 202 | int 203 | server_file_method(struct client *clt) 204 | { 205 | struct http_descriptor *desc = clt->clt_descreq; 206 | 207 | switch (desc->http_method) { 208 | case HTTP_METHOD_GET: 209 | case HTTP_METHOD_HEAD: 210 | return (0); 211 | default: 212 | /* Other methods are not allowed */ 213 | errno = EACCES; 214 | return (405); 215 | } 216 | /* NOTREACHED */ 217 | } 218 | 219 | int 220 | server_file_request(struct httpd *env, struct client *clt, char *path, 221 | struct stat *st) 222 | { 223 | struct server_config *srv_conf = clt->clt_srv_conf; 224 | struct media_type *media; 225 | const char *errstr = NULL; 226 | int fd = -1, ret, code = 500; 227 | 228 | if ((ret = server_file_method(clt)) != 0) { 229 | code = ret; 230 | goto abort; 231 | } 232 | 233 | if ((ret = server_file_modified_since(clt->clt_descreq, st)) != -1) 234 | return (ret); 235 | 236 | /* Now open the file, should be readable or we have another problem */ 237 | if ((fd = open(path, O_RDONLY)) == -1) 238 | goto abort; 239 | 240 | media = media_find_config(env, srv_conf, path); 241 | ret = server_response_http(clt, 200, media, st->st_size, 242 | MINIMUM(time(NULL), st->st_mtim.tv_sec)); 243 | switch (ret) { 244 | case -1: 245 | goto fail; 246 | case 0: 247 | /* Connection is already finished */ 248 | close(fd); 249 | goto done; 250 | default: 251 | break; 252 | } 253 | 254 | clt->clt_fd = fd; 255 | if (clt->clt_srvbev != NULL) 256 | bufferevent_free(clt->clt_srvbev); 257 | 258 | clt->clt_srvbev_throttled = 0; 259 | clt->clt_srvbev = bufferevent_new(clt->clt_fd, server_read, 260 | server_write, server_file_error, clt); 261 | if (clt->clt_srvbev == NULL) { 262 | errstr = "failed to allocate file buffer event"; 263 | goto fail; 264 | } 265 | 266 | /* Adjust read watermark to the socket output buffer size */ 267 | bufferevent_setwatermark(clt->clt_srvbev, EV_READ, 0, 268 | clt->clt_sndbufsiz); 269 | 270 | bufferevent_settimeout(clt->clt_srvbev, 271 | srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec); 272 | bufferevent_enable(clt->clt_srvbev, EV_READ); 273 | bufferevent_disable(clt->clt_bev, EV_READ); 274 | 275 | done: 276 | server_reset_http(clt); 277 | return (0); 278 | fail: 279 | bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE); 280 | bufferevent_free(clt->clt_bev); 281 | clt->clt_bev = NULL; 282 | abort: 283 | if (fd != -1) 284 | close(fd); 285 | if (errstr == NULL) 286 | errstr = strerror(errno); 287 | server_abort_http(clt, code, errstr); 288 | return (-1); 289 | } 290 | 291 | int 292 | server_partial_file_request(struct httpd *env, struct client *clt, char *path, 293 | struct stat *st, char *range_str) 294 | { 295 | struct server_config *srv_conf = clt->clt_srv_conf; 296 | struct http_descriptor *resp = clt->clt_descresp; 297 | struct http_descriptor *desc = clt->clt_descreq; 298 | struct media_type *media, multipart_media; 299 | struct range_data *r = &clt->clt_ranges; 300 | struct range *range; 301 | size_t content_length = 0; 302 | int code = 500, fd = -1, i, nranges, ret; 303 | char content_range[64]; 304 | const char *errstr = NULL; 305 | 306 | /* Ignore range request for methods other than GET */ 307 | if (desc->http_method != HTTP_METHOD_GET) 308 | return server_file_request(env, clt, path, st); 309 | 310 | if ((nranges = parse_ranges(clt, range_str, st->st_size)) < 1) { 311 | code = 416; 312 | (void)snprintf(content_range, sizeof(content_range), 313 | "bytes */%lld", st->st_size); 314 | errstr = content_range; 315 | goto abort; 316 | } 317 | 318 | /* Now open the file, should be readable or we have another problem */ 319 | if ((fd = open(path, O_RDONLY)) == -1) 320 | goto abort; 321 | 322 | media = media_find_config(env, srv_conf, path); 323 | r->range_media = media; 324 | 325 | if (nranges == 1) { 326 | range = &r->range[0]; 327 | (void)snprintf(content_range, sizeof(content_range), 328 | "bytes %lld-%lld/%lld", range->start, range->end, 329 | st->st_size); 330 | if (kv_add(&resp->http_headers, "Content-Range", 331 | content_range) == NULL) 332 | goto abort; 333 | 334 | range = &r->range[0]; 335 | content_length += range->end - range->start + 1; 336 | } else { 337 | /* Add boundary, all parts will be handled by the callback */ 338 | arc4random_buf(&clt->clt_boundary, sizeof(clt->clt_boundary)); 339 | 340 | /* Calculate Content-Length of the complete multipart body */ 341 | for (i = 0; i < nranges; i++) { 342 | range = &r->range[i]; 343 | 344 | /* calculate Content-Length of the complete body */ 345 | if ((ret = snprintf(NULL, 0, 346 | "\r\n--%llu\r\n" 347 | "Content-Type: %s/%s\r\n" 348 | "Content-Range: bytes %lld-%lld/%lld\r\n\r\n", 349 | clt->clt_boundary, 350 | media->media_type, media->media_subtype, 351 | range->start, range->end, st->st_size)) < 0) 352 | goto abort; 353 | 354 | /* Add data length */ 355 | content_length += ret + range->end - range->start + 1; 356 | 357 | } 358 | if ((ret = snprintf(NULL, 0, "\r\n--%llu--\r\n", 359 | clt->clt_boundary)) < 0) 360 | goto abort; 361 | content_length += ret; 362 | 363 | /* prepare multipart/byteranges media type */ 364 | (void)strlcpy(multipart_media.media_type, "multipart", 365 | sizeof(multipart_media.media_type)); 366 | (void)snprintf(multipart_media.media_subtype, 367 | sizeof(multipart_media.media_subtype), 368 | "byteranges; boundary=%llu", clt->clt_boundary); 369 | media = &multipart_media; 370 | } 371 | 372 | /* Start with first range */ 373 | r->range_toread = TOREAD_HTTP_RANGE; 374 | 375 | ret = server_response_http(clt, 206, media, content_length, 376 | MINIMUM(time(NULL), st->st_mtim.tv_sec)); 377 | switch (ret) { 378 | case -1: 379 | goto fail; 380 | case 0: 381 | /* Connection is already finished */ 382 | close(fd); 383 | goto done; 384 | default: 385 | break; 386 | } 387 | 388 | clt->clt_fd = fd; 389 | if (clt->clt_srvbev != NULL) 390 | bufferevent_free(clt->clt_srvbev); 391 | 392 | clt->clt_srvbev_throttled = 0; 393 | clt->clt_srvbev = bufferevent_new(clt->clt_fd, server_read_httprange, 394 | server_write, server_file_error, clt); 395 | if (clt->clt_srvbev == NULL) { 396 | errstr = "failed to allocate file buffer event"; 397 | goto fail; 398 | } 399 | 400 | /* Adjust read watermark to the socket output buffer size */ 401 | bufferevent_setwatermark(clt->clt_srvbev, EV_READ, 0, 402 | clt->clt_sndbufsiz); 403 | 404 | bufferevent_settimeout(clt->clt_srvbev, 405 | srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec); 406 | bufferevent_enable(clt->clt_srvbev, EV_READ); 407 | bufferevent_disable(clt->clt_bev, EV_READ); 408 | 409 | done: 410 | server_reset_http(clt); 411 | return (0); 412 | fail: 413 | bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE); 414 | bufferevent_free(clt->clt_bev); 415 | clt->clt_bev = NULL; 416 | abort: 417 | if (fd != -1) 418 | close(fd); 419 | if (errstr == NULL) 420 | errstr = strerror(errno); 421 | server_abort_http(clt, code, errstr); 422 | return (-1); 423 | } 424 | 425 | int 426 | server_file_index(struct httpd *env, struct client *clt, struct stat *st) 427 | { 428 | char path[PATH_MAX]; 429 | char tmstr[21]; 430 | struct http_descriptor *desc = clt->clt_descreq; 431 | struct server_config *srv_conf = clt->clt_srv_conf; 432 | struct dirent **namelist, *dp; 433 | int namesize, i, ret, fd = -1, namewidth, skip; 434 | int code = 500; 435 | struct evbuffer *evb = NULL; 436 | struct media_type *media; 437 | const char *stripped, *style; 438 | char *escapeduri, *escapedhtml, *escapedpath; 439 | struct tm tm; 440 | time_t t, dir_mtime; 441 | 442 | if ((ret = server_file_method(clt)) != 0) { 443 | code = ret; 444 | goto abort; 445 | } 446 | 447 | /* Request path is already canonicalized */ 448 | stripped = server_root_strip(desc->http_path, srv_conf->strip); 449 | if ((size_t)snprintf(path, sizeof(path), "%s%s", 450 | srv_conf->root, stripped) >= sizeof(path)) 451 | goto abort; 452 | 453 | /* Now open the file, should be readable or we have another problem */ 454 | if ((fd = open(path, O_RDONLY)) == -1) 455 | goto abort; 456 | 457 | /* Save last modification time */ 458 | dir_mtime = MINIMUM(time(NULL), st->st_mtim.tv_sec); 459 | 460 | if ((evb = evbuffer_new()) == NULL) 461 | goto abort; 462 | 463 | if ((namesize = scandir(path, &namelist, NULL, alphasort)) == -1) 464 | goto abort; 465 | 466 | /* Indicate failure but continue going through the list */ 467 | skip = 0; 468 | 469 | if ((escapedpath = escape_html(desc->http_path)) == NULL) 470 | goto fail; 471 | 472 | /* A CSS stylesheet allows minimal customization by the user */ 473 | style = "body { background-color: white; color: black; font-family: " 474 | "sans-serif; }\nhr { border: 0; border-bottom: 1px dashed; }\n"; 475 | /* Generate simple HTML index document */ 476 | if (evbuffer_add_printf(evb, 477 | "\n" 478 | "\n" 479 | "\n" 480 | "\n" 482 | "Index of %s\n" 483 | "\n" 484 | "\n" 485 | "\n" 486 | "

Index of %s

\n" 487 | "
\n
\n",
488 | 	    escapedpath, style, escapedpath) == -1)
489 | 		skip = 1;
490 | 
491 | 	free(escapedpath);
492 | 
493 | 	for (i = 0; i < namesize; i++) {
494 | 		dp = namelist[i];
495 | 
496 | 		if (skip ||
497 | 		    fstatat(fd, dp->d_name, st, 0) == -1) {
498 | 			free(dp);
499 | 			continue;
500 | 		}
501 | 
502 | 		t = st->st_mtime;
503 | 		localtime_r(&t, &tm);
504 | 		strftime(tmstr, sizeof(tmstr), "%d-%h-%Y %R", &tm);
505 | 		namewidth = 51 - strlen(dp->d_name);
506 | 
507 | 		if ((escapeduri = url_encode(dp->d_name)) == NULL)
508 | 			goto fail;
509 | 		if ((escapedhtml = escape_html(dp->d_name)) == NULL)
510 | 			goto fail;
511 | 
512 | 		if (dp->d_name[0] == '.' &&
513 | 		    !(dp->d_name[1] == '.' && dp->d_name[2] == '\0')) {
514 | 			/* ignore hidden files starting with a dot */
515 | 		} else if (S_ISDIR(st->st_mode)) {
516 | 			namewidth -= 1; /* trailing slash */
517 | 			if (evbuffer_add_printf(evb,
518 | 			    "%s/%*s%s%20s\n",
519 | 			    strchr(escapeduri, ':') != NULL ? "./" : "",
520 | 			    escapeduri, escapedhtml,
521 | 			    MAXIMUM(namewidth, 0), " ", tmstr, "-") == -1)
522 | 				skip = 1;
523 | 		} else if (S_ISREG(st->st_mode)) {
524 | 			if (evbuffer_add_printf(evb,
525 | 			    "%s%*s%s%20llu\n",
526 | 			    strchr(escapeduri, ':') != NULL ? "./" : "",
527 | 			    escapeduri, escapedhtml,
528 | 			    MAXIMUM(namewidth, 0), " ",
529 | 			    tmstr, st->st_size) == -1)
530 | 				skip = 1;
531 | 		}
532 | 		free(escapeduri);
533 | 		free(escapedhtml);
534 | 		free(dp);
535 | 	}
536 | 	free(namelist);
537 | 
538 | 	if (skip ||
539 | 	    evbuffer_add_printf(evb,
540 | 	    "
\n
\n\n\n") == -1) 541 | goto abort; 542 | 543 | close(fd); 544 | fd = -1; 545 | 546 | media = media_find_config(env, srv_conf, "index.html"); 547 | ret = server_response_http(clt, 200, media, EVBUFFER_LENGTH(evb), 548 | dir_mtime); 549 | switch (ret) { 550 | case -1: 551 | goto fail; 552 | case 0: 553 | /* Connection is already finished */ 554 | evbuffer_free(evb); 555 | goto done; 556 | default: 557 | break; 558 | } 559 | 560 | if (server_bufferevent_write_buffer(clt, evb) == -1) 561 | goto fail; 562 | evbuffer_free(evb); 563 | evb = NULL; 564 | 565 | bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE); 566 | if (clt->clt_persist) 567 | clt->clt_toread = TOREAD_HTTP_HEADER; 568 | else 569 | clt->clt_toread = TOREAD_HTTP_NONE; 570 | clt->clt_done = 0; 571 | 572 | done: 573 | server_reset_http(clt); 574 | return (0); 575 | fail: 576 | bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE); 577 | bufferevent_free(clt->clt_bev); 578 | clt->clt_bev = NULL; 579 | abort: 580 | if (fd != -1) 581 | close(fd); 582 | if (evb != NULL) 583 | evbuffer_free(evb); 584 | server_abort_http(clt, code, desc->http_path); 585 | return (-1); 586 | } 587 | 588 | void 589 | server_file_error(struct bufferevent *bev, short error, void *arg) 590 | { 591 | struct client *clt = arg; 592 | struct evbuffer *src, *dst; 593 | 594 | if (error & EVBUFFER_TIMEOUT) { 595 | server_close(clt, "buffer event timeout"); 596 | return; 597 | } 598 | if (error & EVBUFFER_ERROR) { 599 | if (errno == EFBIG) { 600 | bufferevent_enable(bev, EV_READ); 601 | return; 602 | } 603 | server_close(clt, "buffer event error"); 604 | return; 605 | } 606 | if (error & (EVBUFFER_READ|EVBUFFER_WRITE|EVBUFFER_EOF)) { 607 | bufferevent_disable(bev, EV_READ|EV_WRITE); 608 | 609 | clt->clt_done = 1; 610 | 611 | src = EVBUFFER_INPUT(clt->clt_bev); 612 | 613 | /* Close the connection if a previous pipeline is empty */ 614 | if (clt->clt_pipelining && EVBUFFER_LENGTH(src) == 0) 615 | clt->clt_persist = 0; 616 | 617 | if (clt->clt_persist) { 618 | /* Close input file and wait for next HTTP request */ 619 | if (clt->clt_fd != -1) 620 | close(clt->clt_fd); 621 | clt->clt_fd = -1; 622 | clt->clt_toread = TOREAD_HTTP_HEADER; 623 | server_reset_http(clt); 624 | bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE); 625 | 626 | /* Start pipelining if the buffer is not empty */ 627 | if (EVBUFFER_LENGTH(src)) { 628 | clt->clt_pipelining++; 629 | server_read_http(clt->clt_bev, arg); 630 | } 631 | return; 632 | } 633 | 634 | dst = EVBUFFER_OUTPUT(clt->clt_bev); 635 | if (EVBUFFER_LENGTH(dst)) { 636 | /* Finish writing all data first */ 637 | bufferevent_enable(clt->clt_bev, EV_WRITE); 638 | return; 639 | } 640 | 641 | server_close(clt, "done"); 642 | return; 643 | } 644 | server_close(clt, "unknown event error"); 645 | return; 646 | } 647 | 648 | int 649 | server_file_modified_since(struct http_descriptor *desc, struct stat *st) 650 | { 651 | struct kv key, *since; 652 | struct tm tm; 653 | 654 | key.kv_key = "If-Modified-Since"; 655 | if ((since = kv_find(&desc->http_headers, &key)) != NULL && 656 | since->kv_value != NULL) { 657 | memset(&tm, 0, sizeof(struct tm)); 658 | 659 | /* 660 | * Return "Not modified" if the file hasn't changed since 661 | * the requested time. 662 | */ 663 | if (strptime(since->kv_value, 664 | "%a, %d %h %Y %T %Z", &tm) != NULL && 665 | timegm(&tm) >= st->st_mtim.tv_sec) 666 | return (304); 667 | } 668 | 669 | return (-1); 670 | } 671 | 672 | int 673 | parse_ranges(struct client *clt, char *str, size_t file_sz) 674 | { 675 | int i = 0; 676 | char *p, *q; 677 | struct range_data *r = &clt->clt_ranges; 678 | 679 | memset(r, 0, sizeof(*r)); 680 | 681 | /* Extract range unit */ 682 | if ((p = strchr(str, '=')) == NULL) 683 | return (-1); 684 | 685 | *p++ = '\0'; 686 | /* Check if it's a bytes range spec */ 687 | if (strcmp(str, "bytes") != 0) 688 | return (-1); 689 | 690 | while ((q = strchr(p, ',')) != NULL) { 691 | *q++ = '\0'; 692 | 693 | /* Extract start and end positions */ 694 | if (parse_range_spec(p, file_sz, &r->range[i]) == 0) 695 | continue; 696 | 697 | i++; 698 | if (i == SERVER_MAX_RANGES) 699 | return (-1); 700 | 701 | p = q; 702 | } 703 | 704 | if (parse_range_spec(p, file_sz, &r->range[i]) != 0) 705 | i++; 706 | 707 | r->range_total = file_sz; 708 | r->range_count = i; 709 | return (i); 710 | } 711 | 712 | int 713 | parse_range_spec(char *str, size_t size, struct range *r) 714 | { 715 | size_t start_str_len, end_str_len; 716 | char *p, *start_str, *end_str; 717 | const char *errstr; 718 | 719 | if ((p = strchr(str, '-')) == NULL) 720 | return (0); 721 | 722 | *p++ = '\0'; 723 | start_str = str; 724 | end_str = p; 725 | start_str_len = strlen(start_str); 726 | end_str_len = strlen(end_str); 727 | 728 | /* Either 'start' or 'end' is optional but not both */ 729 | if ((start_str_len == 0) && (end_str_len == 0)) 730 | return (0); 731 | 732 | if (end_str_len) { 733 | r->end = strtonum(end_str, 0, LLONG_MAX, &errstr); 734 | if (errstr) 735 | return (0); 736 | 737 | if ((size_t)r->end >= size) 738 | r->end = size - 1; 739 | } else 740 | r->end = size - 1; 741 | 742 | if (start_str_len) { 743 | r->start = strtonum(start_str, 0, LLONG_MAX, &errstr); 744 | if (errstr) 745 | return (0); 746 | 747 | if ((size_t)r->start >= size) 748 | return (0); 749 | } else { 750 | r->start = size - r->end; 751 | r->end = size - 1; 752 | } 753 | 754 | if (r->end < r->start) 755 | return (0); 756 | 757 | return (1); 758 | } 759 | -------------------------------------------------------------------------------- /regress/Makefile: -------------------------------------------------------------------------------- 1 | # $OpenBSD: Makefile,v 1.2 2015/07/16 16:35:57 reyk Exp $ 2 | 3 | SUBDIR += patterns tests 4 | 5 | .include 6 | -------------------------------------------------------------------------------- /regress/patterns/Makefile: -------------------------------------------------------------------------------- 1 | # $OpenBSD: Makefile,v 1.2 2015/06/23 19:33:06 reyk Exp $ 2 | 3 | HTTPDSRC = ${.CURDIR}/../../../../usr.sbin/httpd 4 | 5 | .PATH: ${HTTPDSRC} 6 | 7 | REGRESS_TARGETS= test-patterns 8 | 9 | CLEANFILES += patterns-tester 10 | 11 | #LUA?= lua53 12 | .ifdef LUA 13 | REGRESS_TARGETS+= test-patterns-lua 14 | .endif 15 | 16 | patterns-tester: patterns-tester.c patterns.c patterns.h 17 | ${CC} -o $@ ${CFLAGS} ${.CURDIR}/patterns-tester.c ${HTTPDSRC}/patterns.c -I${HTTPDSRC} 18 | 19 | test-patterns: patterns-tester test-patterns.out test-patterns.in 20 | cat ${.CURDIR}/test-patterns.in | grep -v '^#' | \ 21 | while IFS=' ' read string pattern comments ; do \ 22 | ./patterns-tester "$${string}" "$${pattern}" 2>&1 || true; \ 23 | done | diff -I 'OpenBSD' -u ${.CURDIR}/test-patterns.out - 24 | 25 | test-patterns-lua: patterns-tester.lua test-patterns-lua.out test-patterns.in 26 | cat ${.CURDIR}/test-patterns.in | grep -v '^#' | \ 27 | while IFS=' ' read string pattern comments ; do \ 28 | ${LUA} ${.CURDIR}/patterns-tester.lua "$${string}" "$${pattern}" 2>&1 || true; \ 29 | done | sed "s/.*\/patterns\-tester\.lua/X_PATTERNS_TESTER_X/g" | \ 30 | diff -I 'OpenBSD' -u ${.CURDIR}/test-patterns-lua.out - 31 | 32 | .include 33 | -------------------------------------------------------------------------------- /regress/patterns/patterns-tester.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: patterns-tester.c,v 1.1 2015/06/23 18:03:09 semarie Exp $ */ 2 | /* 3 | * Copyright (c) 2015 Sebastien Marie 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "patterns.h" 24 | 25 | extern char * malloc_options; 26 | 27 | static void read_string(char *, size_t); 28 | static void read_string_stop(void); 29 | 30 | static void 31 | read_string(char *buf, size_t len) 32 | { 33 | size_t i; 34 | 35 | /* init */ 36 | bzero(buf, len); 37 | 38 | /* read */ 39 | if (fgets(buf, len, stdin) == NULL) 40 | err(1, "fgets"); 41 | 42 | /* strip '\n' */ 43 | i = strnlen(buf, len); 44 | if (i != 0) 45 | buf[i-1] = '\0'; 46 | } 47 | 48 | static void 49 | read_string_stop() 50 | { 51 | if (getchar() != EOF) 52 | errx(1, "read_string_stop: too many input"); 53 | } 54 | 55 | int 56 | main(int argc, char *argv[]) 57 | { 58 | char string[1024]; 59 | char pattern[1024]; 60 | struct str_match m; 61 | const char *errstr = NULL; 62 | int ret; 63 | size_t i; 64 | 65 | /* configure malloc */ 66 | malloc_options = "S"; 67 | 68 | /* read testcase */ 69 | if (argc != 3) { 70 | /* from stdin (useful for afl) */ 71 | read_string(string, sizeof(string)); 72 | read_string(pattern, sizeof(pattern)); 73 | read_string_stop(); 74 | } else { 75 | /* from arguments */ 76 | strlcpy(string, argv[1], sizeof(string)); 77 | strlcpy(pattern, argv[2], sizeof(pattern)); 78 | } 79 | 80 | /* print testcase */ 81 | printf("string='%s'\n", string); 82 | printf("pattern='%s'\n", pattern); 83 | 84 | /* test it ! */ 85 | ret = str_match(string, pattern, &m, &errstr); 86 | if (errstr != NULL) 87 | errx(1, "str_match: %s", errstr); 88 | 89 | /* print result */ 90 | printf("ret=%d num=%d\n", ret, m.sm_nmatch); 91 | for (i=0; i 4 | # Copyright (c) 2015 Reyk Floeter 5 | # 6 | # Permission to use, copy, modify, and distribute this software for any 7 | # purpose with or without fee is hereby granted, provided that the above 8 | # copyright notice and this permission notice appear in all copies. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | 18 | use strict; 19 | use warnings; 20 | 21 | package Client; 22 | use parent 'Proc'; 23 | use Carp; 24 | use Socket; 25 | use Socket6; 26 | use IO::Socket; 27 | use IO::Socket::INET6; 28 | use IO::Socket::SSL; 29 | 30 | sub new { 31 | my $class = shift; 32 | my %args = @_; 33 | $args{chroot} ||= "."; 34 | $args{logfile} ||= $args{chroot}."/client.log"; 35 | $args{up} ||= "Connected"; 36 | $args{timefile} //= "time.log"; 37 | my $self = Proc::new($class, %args); 38 | $self->{connectdomain} 39 | or croak "$class connect domain not given"; 40 | $self->{connectaddr} 41 | or croak "$class connect addr not given"; 42 | $self->{connectport} 43 | or croak "$class connect port not given"; 44 | return $self; 45 | } 46 | 47 | sub child { 48 | my $self = shift; 49 | 50 | # in case we redo the connect, shutdown the old one 51 | shutdown(\*STDOUT, SHUT_WR); 52 | delete $self->{cs}; 53 | 54 | $SSL_ERROR = ""; 55 | my $iosocket = $self->{tls} ? "IO::Socket::SSL" : "IO::Socket::INET6"; 56 | my $cs = $iosocket->new( 57 | Proto => "tcp", 58 | Domain => $self->{connectdomain}, 59 | PeerAddr => $self->{connectaddr}, 60 | PeerPort => $self->{connectport}, 61 | SSL_verify_mode => SSL_VERIFY_NONE, 62 | ) or die ref($self), " $iosocket socket connect failed: $!,$SSL_ERROR"; 63 | print STDERR "connect sock: ",$cs->sockhost()," ",$cs->sockport(),"\n"; 64 | print STDERR "connect peer: ",$cs->peerhost()," ",$cs->peerport(),"\n"; 65 | if ($self->{tls}) { 66 | print STDERR "tls version: ",$cs->get_sslversion(),"\n"; 67 | print STDERR "tls cipher: ",$cs->get_cipher(),"\n"; 68 | print STDERR "tls peer certificate:\n", 69 | $cs->dump_peer_certificate(); 70 | } 71 | 72 | *STDIN = *STDOUT = $self->{cs} = $cs; 73 | } 74 | 75 | 1; 76 | -------------------------------------------------------------------------------- /regress/tests/Httpd.pm: -------------------------------------------------------------------------------- 1 | # $OpenBSD: Httpd.pm,v 1.2 2017/01/30 21:18:24 reyk Exp $ 2 | 3 | # Copyright (c) 2010-2015 Alexander Bluhm 4 | # Copyright (c) 2015 Reyk Floeter 5 | # 6 | # Permission to use, copy, modify, and distribute this software for any 7 | # purpose with or without fee is hereby granted, provided that the above 8 | # copyright notice and this permission notice appear in all copies. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | 18 | use strict; 19 | use warnings; 20 | 21 | package Httpd; 22 | use parent 'Proc'; 23 | use Carp; 24 | use File::Basename; 25 | 26 | sub new { 27 | my $class = shift; 28 | my %args = @_; 29 | $args{chroot} ||= "."; 30 | $args{docroot} ||= "htdocs"; 31 | $args{logfile} ||= $args{chroot}."/httpd.log"; 32 | $args{up} ||= $args{dryrun} || "server_launch: "; 33 | $args{down} ||= $args{dryrun} ? "httpd.conf:" : "parent terminating"; 34 | $args{func} = sub { Carp::confess "$class func may not be called" }; 35 | $args{conffile} ||= "httpd.conf"; 36 | my $self = Proc::new($class, %args); 37 | ref($self->{http}) eq 'ARRAY' 38 | or $self->{http} = [ split("\n", $self->{http} || "") ]; 39 | $self->{listenaddr} 40 | or croak "$class listen addr not given"; 41 | $self->{listenport} 42 | or croak "$class listen port not given"; 43 | 44 | my $test = basename($self->{testfile} || ""); 45 | # tls does not allow a too long session id, so truncate it 46 | substr($test, 25, length($test) - 25, "") if length($test) > 25; 47 | open(my $fh, '>', $self->{conffile}) 48 | or die ref($self), " conf file $self->{conffile} create failed: $!"; 49 | 50 | # substitute variables in config file 51 | my $curdir = dirname($0) || "."; 52 | my $connectport = $self->{connectport}; 53 | my $connectaddr = $self->{connectaddr}; 54 | my $listenaddr = $self->{listenaddr}; 55 | my $listenport = $self->{listenport}; 56 | 57 | print $fh "prefork 1\n"; # only crashes of first child are observed 58 | print $fh "chroot \"".$args{docroot}."\"\n"; 59 | print $fh "logdir \"".$args{chroot}."\"\n"; 60 | 61 | my @http = @{$self->{http}}; 62 | print $fh "server \"www.$test.local\" {"; 63 | my $tls = $self->{listentls} ? "tls " : ""; 64 | print $fh "\n\tlisten on $self->{listenaddr} ". 65 | "${tls}port $self->{listenport}" unless grep { /^listen / } @http; 66 | # substitute variables in config file 67 | foreach (@http) { 68 | s/(\$[a-z]+)/$1/eeg; 69 | } 70 | print $fh map { "\n\t$_" } @http; 71 | if ($self->{listentls}) { 72 | print $fh "\n"; 73 | print $fh "\ttls certificate \"".$args{chroot}."/server.crt\"\n"; 74 | print $fh "\ttls key \"".$args{chroot}."/server.key\""; 75 | } 76 | print $fh "\n\troot \"/\""; 77 | print $fh "\n\tlog style combined"; 78 | print $fh "\n}\n"; 79 | 80 | return $self; 81 | } 82 | 83 | sub child { 84 | my $self = shift; 85 | my @sudo = $ENV{SUDO} ? $ENV{SUDO} : (); 86 | my @ktrace = $ENV{KTRACE} ? ($ENV{KTRACE}, "-i") : (); 87 | my $httpd = $ENV{HTTPD} ? $ENV{HTTPD} : "httpd"; 88 | my @cmd = (@sudo, @ktrace, $httpd, "-dvv", "-f", $self->{conffile}); 89 | print STDERR "execute: @cmd\n"; 90 | exec @cmd; 91 | die ref($self), " exec '@cmd' failed: $!"; 92 | } 93 | 94 | 1; 95 | -------------------------------------------------------------------------------- /regress/tests/LICENSE: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010-2017 Alexander Bluhm 2 | # Copyright (c) 2014,2015 Reyk Floeter 3 | # 4 | # Permission to use, copy, modify, and distribute this software for any 5 | # purpose with or without fee is hereby granted, provided that the above 6 | # copyright notice and this permission notice appear in all copies. 7 | # 8 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | -------------------------------------------------------------------------------- /regress/tests/Makefile: -------------------------------------------------------------------------------- 1 | # $OpenBSD: Makefile,v 1.8 2017/07/14 13:31:44 bluhm Exp $ 2 | 3 | # The following ports must be installed for the regression tests: 4 | # p5-IO-Socket-INET6 object interface for AF_INET and AF_INET6 domain sockets 5 | # p5-Socket6 Perl defines relating to AF_INET6 sockets 6 | # p5-IO-Socket-SSL perl interface to SSL sockets 7 | # 8 | # Check wether all required perl packages are installed. If some 9 | # are missing print a warning and skip the tests, but do not fail. 10 | 11 | PERL_REQUIRE != perl -Mstrict -Mwarnings -e ' \ 12 | eval { require IO::Socket::INET6 } or print $@; \ 13 | eval { require Socket6 } or print $@; \ 14 | eval { require IO::Socket::SSL } or print $@; \ 15 | ' 16 | .if ! empty (PERL_REQUIRE) 17 | regress: 18 | @echo "${PERL_REQUIRE}" 19 | @echo install these perl packages for additional tests 20 | @echo SKIPPED 21 | .endif 22 | 23 | # Automatically generate regress targets from test cases in directory. 24 | 25 | ARGS != cd ${.CURDIR} && ls args-*.pl 26 | TARGETS ?= ${ARGS} 27 | REGRESS_TARGETS = ${TARGETS:S/^/run-regress-/} 28 | CLEANFILES += *.log httpd.conf ktrace.out stamp-* 29 | CLEANFILES += *.pem *.req *.crt *.key *.srl md5-* 30 | 31 | HTDOCS_FILES = 512 1048576 1073741824 32 | HTDOCS_MD5 = ${HTDOCS_FILES:S,^,md5-,} 33 | HTDOCS_SPARSE = yes 34 | CLEANFILES += htdocs/* 35 | 36 | # Set variables so that make runs with and without obj directory. 37 | # Only do that if necessary to keep visible output short. 38 | 39 | .if ${.CURDIR} == ${.OBJDIR} 40 | PERLINC = 41 | PERLPATH = 42 | .else 43 | PERLINC = -I${.CURDIR} 44 | PERLPATH = ${.CURDIR}/ 45 | .endif 46 | 47 | # The arg tests take a perl hash with arguments controlling the 48 | # test parameters. Generally they consist of client, httpd, server. 49 | 50 | .for a in ${ARGS} 51 | run-regress-$a: $a ${HTDOCS_MD5} 52 | @echo '\n======== $@ ========' 53 | time SUDO=${SUDO} KTRACE=${KTRACE} HTTPD=${HTTPD} perl ${PERLINC} ${PERLPATH}httpd.pl ${.OBJDIR} ${PERLPATH}$a 54 | .endfor 55 | 56 | # populate htdocs 57 | 58 | .for d in ${HTDOCS_FILES} 59 | htdocs/$d: 60 | @echo '\n======== file: $d ========' 61 | mkdir -m 0755 -p ${@:H} 62 | .if (${HTDOCS_SPARSE} != "yes") 63 | dd if=/dev/arandom of=$@ count=$$(($d / 512)) bs=512 64 | .else 65 | dd of=$@ seek=$$(($d / 512)) bs=512 count=0 status=none 66 | .endif 67 | 68 | md5-$d: htdocs/$d 69 | md5 -q htdocs/$d >$@ 70 | .endfor 71 | 72 | # create certificates for TLS 73 | 74 | ca.crt: 75 | openssl req -batch -new -subj /L=OpenBSD/O=httpd-regress/OU=ca/CN=root/ -nodes -newkey rsa -keyout ca.key -x509 -out ca.crt 76 | 77 | server.req: 78 | openssl req -batch -new -subj /L=OpenBSD/O=httpd-regress/OU=server/CN=localhost/ -nodes -newkey rsa -keyout server.key -out server.req 79 | 80 | server.crt: ca.crt server.req 81 | openssl x509 -CAcreateserial -CAkey ca.key -CA ca.crt -req -in server.req -out server.crt 82 | 83 | ${REGRESS_TARGETS:M*tls*} ${REGRESS_TARGETS:M*https*}: server.crt 84 | 85 | # make perl syntax check for all args files 86 | 87 | .PHONY: syntax 88 | 89 | syntax: stamp-syntax 90 | 91 | stamp-syntax: ${ARGS} 92 | .for a in ${ARGS} 93 | @perl -c ${PERLPATH}$a 94 | .endfor 95 | @date >$@ 96 | 97 | .include 98 | -------------------------------------------------------------------------------- /regress/tests/Proc.pm: -------------------------------------------------------------------------------- 1 | # $OpenBSD: Proc.pm,v 1.2 2016/05/03 19:13:04 bluhm Exp $ 2 | 3 | # Copyright (c) 2010-2014 Alexander Bluhm 4 | # 5 | # Permission to use, copy, modify, and distribute this software for any 6 | # purpose with or without fee is hereby granted, provided that the above 7 | # copyright notice and this permission notice appear in all copies. 8 | # 9 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | 17 | use strict; 18 | use warnings; 19 | 20 | package Proc; 21 | use Carp; 22 | use Errno; 23 | use File::Basename; 24 | use IO::File; 25 | use POSIX; 26 | use Time::HiRes qw(time alarm sleep); 27 | 28 | my %CHILDREN; 29 | 30 | sub kill_children { 31 | my @pids = @_ ? @_ : keys %CHILDREN 32 | or return; 33 | my @perms; 34 | foreach my $pid (@pids) { 35 | if (kill(TERM => $pid) != 1 and $!{EPERM}) { 36 | push @perms, $pid; 37 | } 38 | } 39 | if (my $sudo = $ENV{SUDO} and @perms) { 40 | local $?; # do not modify during END block 41 | my @cmd = ($sudo, '/bin/kill', '-TERM', @perms); 42 | system(@cmd); 43 | } 44 | delete @CHILDREN{@pids}; 45 | } 46 | 47 | BEGIN { 48 | $SIG{TERM} = $SIG{INT} = sub { 49 | my $sig = shift; 50 | kill_children(); 51 | $SIG{TERM} = $SIG{INT} = 'DEFAULT'; 52 | POSIX::raise($sig); 53 | }; 54 | } 55 | 56 | END { 57 | kill_children(); 58 | $SIG{TERM} = $SIG{INT} = 'DEFAULT'; 59 | } 60 | 61 | sub new { 62 | my $class = shift; 63 | my $self = { @_ }; 64 | $self->{down} ||= "Shutdown"; 65 | $self->{func} && ref($self->{func}) eq 'CODE' 66 | or croak "$class func not given"; 67 | $self->{logfile} 68 | or croak "$class log file not given"; 69 | open(my $fh, '>', $self->{logfile}) 70 | or die "$class log file $self->{logfile} create failed: $!"; 71 | $fh->autoflush; 72 | $self->{log} = $fh; 73 | return bless $self, $class; 74 | } 75 | 76 | sub run { 77 | my $self = shift; 78 | 79 | pipe(my $reader, my $writer) 80 | or die ref($self), " pipe to child failed: $!"; 81 | defined(my $pid = fork()) 82 | or die ref($self), " fork child failed: $!"; 83 | if ($pid) { 84 | $CHILDREN{$pid} = 1; 85 | $self->{pid} = $pid; 86 | close($reader); 87 | $self->{pipe} = $writer; 88 | return $self; 89 | } 90 | %CHILDREN = (); 91 | $SIG{TERM} = $SIG{INT} = 'DEFAULT'; 92 | $SIG{__DIE__} = sub { 93 | die @_ if $^S; 94 | warn @_; 95 | IO::Handle::flush(\*STDERR); 96 | POSIX::_exit(255); 97 | }; 98 | open(STDERR, '>&', $self->{log}) 99 | or die ref($self), " dup STDERR failed: $!"; 100 | close($writer); 101 | open(STDIN, '<&', $reader) 102 | or die ref($self), " dup STDIN failed: $!"; 103 | close($reader); 104 | 105 | do { 106 | $self->child(); 107 | print STDERR $self->{up}, "\n"; 108 | $self->{begin} = time(); 109 | $self->{func}->($self); 110 | } while ($self->{redo}); 111 | $self->{end} = time(); 112 | print STDERR "Shutdown", "\n"; 113 | if ($self->{timefile}) { 114 | open(my $fh, '>>', $self->{timefile}) 115 | or die ref($self), " open $self->{timefile} failed: $!"; 116 | printf $fh "time='%s' duration='%.10g' ". 117 | "test='%s'\n", 118 | scalar(localtime(time())), $self->{end} - $self->{begin}, 119 | basename($self->{testfile}); 120 | } 121 | 122 | IO::Handle::flush(\*STDOUT); 123 | IO::Handle::flush(\*STDERR); 124 | POSIX::_exit(0); 125 | } 126 | 127 | sub wait { 128 | my $self = shift; 129 | my $flags = shift; 130 | 131 | my $pid = $self->{pid} 132 | or croak ref($self), " no child pid"; 133 | my $kid = waitpid($pid, $flags); 134 | if ($kid > 0) { 135 | my $status = $?; 136 | my $code; 137 | $code = "exit: ". WEXITSTATUS($?) if WIFEXITED($?); 138 | $code = "signal: ". WTERMSIG($?) if WIFSIGNALED($?); 139 | $code = "stop: ". WSTOPSIG($?) if WIFSTOPPED($?); 140 | delete $CHILDREN{$pid} if WIFEXITED($?) || WIFSIGNALED($?); 141 | return wantarray ? ($kid, $status, $code) : $kid; 142 | } 143 | return $kid; 144 | } 145 | 146 | sub loggrep { 147 | my $self = shift; 148 | my($regex, $timeout) = @_; 149 | 150 | my $end; 151 | $end = time() + $timeout if $timeout; 152 | 153 | do { 154 | my($kid, $status, $code) = $self->wait(WNOHANG); 155 | if ($kid > 0 && $status != 0 && !$self->{dryrun}) { 156 | # child terminated with failure 157 | die ref($self), " child status: $status $code"; 158 | } 159 | open(my $fh, '<', $self->{logfile}) 160 | or die ref($self), " log file open failed: $!"; 161 | my @match = grep { /$regex/ } <$fh>; 162 | return wantarray ? @match : $match[0] if @match; 163 | close($fh); 164 | # pattern not found 165 | if ($kid == 0) { 166 | # child still running, wait for log data 167 | sleep .1; 168 | } else { 169 | # child terminated, no new log data possible 170 | return; 171 | } 172 | } while ($timeout and time() < $end); 173 | 174 | return; 175 | } 176 | 177 | sub up { 178 | my $self = shift; 179 | my $timeout = shift || 10; 180 | $self->loggrep(qr/$self->{up}/, $timeout) 181 | or croak ref($self), " no '$self->{up}' in $self->{logfile} ". 182 | "after $timeout seconds"; 183 | return $self; 184 | } 185 | 186 | sub down { 187 | my $self = shift; 188 | my $timeout = shift || 300; 189 | $self->loggrep(qr/$self->{down}/, $timeout) 190 | or croak ref($self), " no '$self->{down}' in $self->{logfile} ". 191 | "after $timeout seconds"; 192 | return $self; 193 | } 194 | 195 | sub kill_child { 196 | my $self = shift; 197 | kill_children($self->{pid}); 198 | return $self; 199 | } 200 | 201 | 1; 202 | -------------------------------------------------------------------------------- /regress/tests/README: -------------------------------------------------------------------------------- 1 | Run httpd regression tests. The framework runs a client and an httpd. 2 | Each test creates a special httpd.conf and starts those two processes. 3 | All processes write log files that are checked for certain messages. 4 | The test arguments are kept in the args-*.pl files. 5 | 6 | SUDO=doas 7 | As httpd needs root privileges, either run the tests as root or set 8 | this variable and run make as a regular user. Only the code that 9 | requires it is run as root. 10 | 11 | KTRACE=ktrace 12 | Set this variable if you want a ktrace output from httpd. Note that 13 | ktrace is invoked after SUDO as SUDO would disable it. 14 | 15 | HTTPD=/usr/src/usr.sbin/httpd/obj/httpd 16 | Start an alternative httpd program that is not in the path. 17 | 18 | HTDOCS_SPARSE=no 19 | Set to anything other than "yes" to create real test files instead of 20 | sparse files. This needs more than 1G of free disk space. 21 | -------------------------------------------------------------------------------- /regress/tests/args-default.pl: -------------------------------------------------------------------------------- 1 | # test default values 2 | 3 | use strict; 4 | use warnings; 5 | 6 | our %args = ( 7 | len => 512, 8 | md5 => path_md5("512") 9 | ); 10 | 11 | 1; 12 | -------------------------------------------------------------------------------- /regress/tests/args-get-1048576.pl: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | my $len = 1048576; 5 | our %args = ( 6 | client => { 7 | path => "$len", 8 | len => $len, 9 | http_vers => [ "1.0" ], 10 | }, 11 | len => 1048576, 12 | md5 => path_md5("$len") 13 | ); 14 | 15 | 1; 16 | -------------------------------------------------------------------------------- /regress/tests/args-get-1073741824.pl: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | my $len = 1073741824; 5 | my @lengths = ($len, $len); 6 | our %args = ( 7 | client => { 8 | path => "$len", 9 | http_vers => [ "1.0" ], 10 | lengths => \@lengths, 11 | }, 12 | md5 => path_md5("$len"), 13 | lengths => \@lengths, 14 | ); 15 | 16 | 1; 17 | -------------------------------------------------------------------------------- /regress/tests/args-get-512.pl: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | my $len = 512; 5 | my @lengths = ($len, $len, $len); 6 | our %args = ( 7 | client => { 8 | path => "$len", 9 | http_vers => [ "1.0" ], 10 | lengths => \@lengths, 11 | }, 12 | md5 => path_md5("$len"), 13 | lengths => \@lengths, 14 | ); 15 | 16 | 1; 17 | -------------------------------------------------------------------------------- /regress/tests/args-get-slash.pl: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | our %args = ( 5 | client => { 6 | func => sub { 7 | my $self = shift; 8 | print "GET /\r\n\r\n"; 9 | }, 10 | nocheck => 1 11 | }, 12 | httpd => { 13 | loggrep => { 14 | qr/"GET \/" 400 0/ => 1, 15 | }, 16 | }, 17 | ); 18 | 19 | 1; 20 | 21 | -------------------------------------------------------------------------------- /regress/tests/args-log-user-agent.pl: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | our %args = ( 5 | client => { 6 | header => { 7 | "User-Agent" => "regress\t\n\nGET / HTTP/1.0\r\n" 8 | } 9 | }, 10 | httpd => { 11 | loggrep => { 12 | qr/\"regress\\t\\n\\nGET \/ HTTP\/1\.0\"/ => 1, 13 | }, 14 | }, 15 | ); 16 | 17 | 1; 18 | -------------------------------------------------------------------------------- /regress/tests/args-tls-get-1073741824.pl: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | my $len = 1073741824; 5 | our %args = ( 6 | client => { 7 | tls => 1, 8 | path => "$len", 9 | len => $len, 10 | }, 11 | httpd => { 12 | listentls => 1, 13 | }, 14 | len => $len, 15 | md5 => path_md5("$len"), 16 | ); 17 | 18 | 1; 19 | -------------------------------------------------------------------------------- /regress/tests/args-tls.pl: -------------------------------------------------------------------------------- 1 | # test https connection 2 | 3 | use strict; 4 | use warnings; 5 | 6 | our %args = ( 7 | client => { 8 | tls => 1, 9 | loggrep => 'Issuer.*/OU=ca/', 10 | }, 11 | httpd => { 12 | listentls => 1, 13 | }, 14 | len => 512, 15 | md5 => path_md5("512") 16 | ); 17 | 18 | 1; 19 | -------------------------------------------------------------------------------- /regress/tests/funcs.pl: -------------------------------------------------------------------------------- 1 | # $OpenBSD: funcs.pl,v 1.8 2017/07/14 13:31:44 bluhm Exp $ 2 | 3 | # Copyright (c) 2010-2017 Alexander Bluhm 4 | # 5 | # Permission to use, copy, modify, and distribute this software for any 6 | # purpose with or without fee is hereby granted, provided that the above 7 | # copyright notice and this permission notice appear in all copies. 8 | # 9 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | 17 | use strict; 18 | use warnings; 19 | no warnings 'experimental::smartmatch'; 20 | use feature 'switch'; 21 | use Errno; 22 | use Digest::MD5; 23 | use Socket; 24 | use Socket6; 25 | use IO::Socket; 26 | use IO::Socket::INET6; 27 | 28 | sub find_ports { 29 | my %args = @_; 30 | my $num = delete $args{num} // 1; 31 | my $domain = delete $args{domain} // AF_INET; 32 | my $addr = delete $args{addr} // "127.0.0.1"; 33 | 34 | my @sockets = (1..$num); 35 | foreach my $s (@sockets) { 36 | $s = IO::Socket::INET6->new( 37 | Proto => "tcp", 38 | Domain => $domain, 39 | $addr ? (LocalAddr => $addr) : (), 40 | ) or die "find_ports: create and bind socket failed: $!"; 41 | } 42 | my @ports = map { $_->sockport() } @sockets; 43 | 44 | return @ports; 45 | } 46 | 47 | sub path_md5 { 48 | my $name = shift; 49 | my $val = `cat md5-$name`; 50 | } 51 | 52 | ######################################################################## 53 | # Client funcs 54 | ######################################################################## 55 | 56 | sub write_char { 57 | my $self = shift; 58 | my $len = shift // $self->{len} // 512; 59 | my $sleep = $self->{sleep}; 60 | 61 | my $ctx = Digest::MD5->new(); 62 | my $char = '0'; 63 | for (my $i = 1; $i < $len; $i++) { 64 | $ctx->add($char); 65 | print $char 66 | or die ref($self), " print failed: $!"; 67 | given ($char) { 68 | when(/9/) { $char = 'A' } 69 | when(/Z/) { $char = 'a' } 70 | when(/z/) { $char = "\n" } 71 | when(/\n/) { print STDERR "."; $char = '0' } 72 | default { $char++ } 73 | } 74 | if ($self->{sleep}) { 75 | IO::Handle::flush(\*STDOUT); 76 | sleep $self->{sleep}; 77 | } 78 | } 79 | if ($len) { 80 | $char = "\n"; 81 | $ctx->add($char); 82 | print $char 83 | or die ref($self), " print failed: $!"; 84 | print STDERR ".\n"; 85 | } 86 | IO::Handle::flush(\*STDOUT); 87 | 88 | print STDERR "LEN: ", $len, "\n"; 89 | print STDERR "MD5: ", $ctx->hexdigest, "\n"; 90 | } 91 | 92 | sub http_client { 93 | my $self = shift; 94 | 95 | unless ($self->{lengths}) { 96 | # only a single http request 97 | my $len = shift // $self->{len} // 512; 98 | my $cookie = $self->{cookie}; 99 | http_request($self, $len, "1.0", $cookie); 100 | http_response($self, $len); 101 | return; 102 | } 103 | 104 | $self->{http_vers} ||= ["1.1", "1.0"]; 105 | my $vers = $self->{http_vers}[0]; 106 | my @lengths = @{$self->{redo}{lengths} || $self->{lengths}}; 107 | my @cookies = @{$self->{redo}{cookies} || $self->{cookies} || []}; 108 | while (defined (my $len = shift @lengths)) { 109 | my $cookie = shift @cookies || $self->{cookie}; 110 | eval { 111 | http_request($self, $len, $vers, $cookie); 112 | http_response($self, $len); 113 | }; 114 | warn $@ if $@; 115 | if (@lengths && ($@ || $vers eq "1.0")) { 116 | # reconnect and redo the outstanding requests 117 | $self->{redo} = { 118 | lengths => \@lengths, 119 | cookies => \@cookies, 120 | }; 121 | return; 122 | } 123 | } 124 | delete $self->{redo}; 125 | shift @{$self->{http_vers}}; 126 | if (@{$self->{http_vers}}) { 127 | # run the tests again with other persistence 128 | $self->{redo} = { 129 | lengths => [@{$self->{lengths}}], 130 | cookies => [@{$self->{cookies} || []}], 131 | }; 132 | } 133 | } 134 | 135 | sub http_request { 136 | my ($self, $len, $vers, $cookie) = @_; 137 | my $method = $self->{method} || "GET"; 138 | my %header = %{$self->{header} || {}}; 139 | 140 | # encode the requested length or chunks into the url 141 | my $path = ref($len) eq 'ARRAY' ? join("/", @$len) : $len; 142 | # overwrite path with custom path 143 | if (defined($self->{path})) { 144 | $path = $self->{path}; 145 | } 146 | my @request = ("$method /$path HTTP/$vers"); 147 | push @request, "Host: foo.bar" unless defined $header{Host}; 148 | if ($vers eq "1.1" && $method eq "PUT") { 149 | if (ref($len) eq 'ARRAY') { 150 | push @request, "Transfer-Encoding: chunked" 151 | if !defined $header{'Transfer-Encoding'}; 152 | } else { 153 | push @request, "Content-Length: $len" 154 | if !defined $header{'Content-Length'}; 155 | } 156 | } 157 | foreach my $key (sort keys %header) { 158 | my $val = $header{$key}; 159 | if (ref($val) eq 'ARRAY') { 160 | push @request, "$key: $_" 161 | foreach @{$val}; 162 | } else { 163 | push @request, "$key: $val"; 164 | } 165 | } 166 | push @request, "Cookie: $cookie" if $cookie; 167 | push @request, ""; 168 | print STDERR map { ">>> $_\n" } @request; 169 | print map { "$_\r\n" } @request; 170 | if ($method eq "PUT") { 171 | if (ref($len) eq 'ARRAY') { 172 | if ($vers eq "1.1") { 173 | write_chunked($self, @$len); 174 | } else { 175 | write_char($self, $_) foreach (@$len); 176 | } 177 | } else { 178 | write_char($self, $len); 179 | } 180 | } 181 | IO::Handle::flush(\*STDOUT); 182 | # XXX client shutdown seems to be broken in httpd 183 | #shutdown(\*STDOUT, SHUT_WR) 184 | # or die ref($self), " shutdown write failed: $!" 185 | # if $vers ne "1.1"; 186 | } 187 | 188 | sub http_response { 189 | my ($self, $len) = @_; 190 | my $method = $self->{method} || "GET"; 191 | my $code = $self->{code} || "200 OK"; 192 | 193 | my $vers; 194 | my $chunked = 0; 195 | my $multipart = 0; 196 | my $boundary; 197 | { 198 | local $/ = "\r\n"; 199 | local $_ = ; 200 | defined 201 | or die ref($self), " missing http $len response"; 202 | chomp; 203 | print STDERR "<<< $_\n"; 204 | m{^HTTP/(\d\.\d) $code$} 205 | or die ref($self), " http response not $code" 206 | unless $self->{httpnok}; 207 | $vers = $1; 208 | while () { 209 | chomp; 210 | print STDERR "<<< $_\n"; 211 | last if /^$/; 212 | if (/^Content-Length: (.*)/) { 213 | if ($self->{httpnok} or $self->{multipart}) { 214 | $len = $1; 215 | } else { 216 | $1 == $len or die ref($self), 217 | " bad content length $1"; 218 | } 219 | } 220 | if (/^Transfer-Encoding: chunked$/) { 221 | $chunked = 1; 222 | } 223 | if (/^Content-Type: multipart\/byteranges; boundary=(.*)$/) { 224 | $multipart = 1; 225 | $boundary = $1; 226 | } 227 | } 228 | } 229 | die ref($self), " no multipart response" 230 | if ($self->{multipart} && $multipart == 0); 231 | 232 | if ($multipart) { 233 | read_multipart($self, $boundary); 234 | } elsif ($chunked) { 235 | read_chunked($self); 236 | } else { 237 | read_char($self, $len) 238 | if $method eq "GET"; 239 | } 240 | } 241 | 242 | sub read_chunked { 243 | my $self = shift; 244 | 245 | for (;;) { 246 | my $len; 247 | { 248 | local $/ = "\r\n"; 249 | local $_ = ; 250 | defined or die ref($self), " missing chunk size"; 251 | chomp; 252 | print STDERR "<<< $_\n"; 253 | /^[[:xdigit:]]+$/ 254 | or die ref($self), " chunk size not hex: $_"; 255 | $len = hex; 256 | } 257 | last unless $len > 0; 258 | read_char($self, $len); 259 | { 260 | local $/ = "\r\n"; 261 | local $_ = ; 262 | defined or die ref($self), " missing chunk data end"; 263 | chomp; 264 | print STDERR "<<< $_\n"; 265 | /^$/ or die ref($self), " no chunk data end: $_"; 266 | } 267 | } 268 | { 269 | local $/ = "\r\n"; 270 | while () { 271 | chomp; 272 | print STDERR "<<< $_\n"; 273 | last if /^$/; 274 | } 275 | defined or die ref($self), " missing chunk trailer"; 276 | } 277 | } 278 | 279 | sub read_multipart { 280 | my $self = shift; 281 | my $boundary = shift; 282 | my $ctx = Digest::MD5->new(); 283 | my $len = 0; 284 | 285 | for (;;) { 286 | my $part = 0; 287 | { 288 | local $/ = "\r\n"; 289 | local $_ = ; 290 | local $_ = ; 291 | defined or die ref($self), " missing boundary"; 292 | chomp; 293 | print STDERR "<<< $_\n"; 294 | /^--$boundary(--)?$/ 295 | or die ref($self), " boundary not found: $_"; 296 | if (not $1) { 297 | while () { 298 | chomp; 299 | if (/^Content-Length: (.*)/) { 300 | $part = $1; 301 | } 302 | if (/^Content-Range: bytes (\d+)-(\d+)\/(\d+)$/) { 303 | $part = $2 - $1 + 1; 304 | } 305 | print STDERR "<<< $_\n"; 306 | last if /^$/; 307 | } 308 | } 309 | } 310 | last unless $part > 0; 311 | 312 | $len += read_part($self, $ctx, $part); 313 | } 314 | 315 | print STDERR "LEN: ", $len, "\n"; 316 | print STDERR "MD5: ", $ctx->hexdigest, "\n"; 317 | 318 | } 319 | 320 | sub errignore { 321 | $SIG{PIPE} = 'IGNORE'; 322 | $SIG{__DIE__} = sub { 323 | die @_ if $^S; 324 | warn "Error ignored"; 325 | warn @_; 326 | IO::Handle::flush(\*STDERR); 327 | POSIX::_exit(0); 328 | }; 329 | } 330 | 331 | ######################################################################## 332 | # Common funcs 333 | ######################################################################## 334 | 335 | sub read_char { 336 | my $self = shift; 337 | my $max = shift // $self->{max}; 338 | 339 | my $ctx = Digest::MD5->new(); 340 | my $len = read_part($self, $ctx, $max); 341 | 342 | print STDERR "LEN: ", $len, "\n"; 343 | print STDERR "MD5: ", $ctx->hexdigest, "\n"; 344 | } 345 | 346 | sub read_part { 347 | my $self = shift; 348 | my ($ctx, $max) = @_; 349 | 350 | my $opct = 0; 351 | my $len = 0; 352 | for (;;) { 353 | if (defined($max) && $len >= $max) { 354 | print STDERR "Max\n"; 355 | last; 356 | } 357 | my $rlen = POSIX::BUFSIZ; 358 | if (defined($max) && $rlen > $max - $len) { 359 | $rlen = $max - $len; 360 | } 361 | defined(my $n = read(STDIN, my $buf, $rlen)) 362 | or die ref($self), " read failed: $!"; 363 | $n or last; 364 | $len += $n; 365 | $ctx->add($buf); 366 | my $pct = ($len / $max) * 100.0; 367 | if ($pct >= $opct + 1) { 368 | printf(STDERR "%.2f%% $len/$max\n", $pct); 369 | $opct = $pct; 370 | } 371 | } 372 | return $len; 373 | } 374 | 375 | sub write_chunked { 376 | my $self = shift; 377 | my @chunks = @_; 378 | 379 | foreach my $len (@chunks) { 380 | printf STDERR ">>> %x\n", $len; 381 | printf "%x\r\n", $len; 382 | write_char($self, $len); 383 | printf STDERR ">>> \n"; 384 | print "\r\n"; 385 | } 386 | my @trailer = ("0", "X-Chunk-Trailer: @chunks", ""); 387 | print STDERR map { ">>> $_\n" } @trailer; 388 | print map { "$_\r\n" } @trailer; 389 | } 390 | 391 | ######################################################################## 392 | # Script funcs 393 | ######################################################################## 394 | 395 | sub check_logs { 396 | my ($c, $r, %args) = @_; 397 | 398 | return if $args{nocheck}; 399 | 400 | check_len($c, $r, %args); 401 | check_md5($c, $r, %args); 402 | check_loggrep($c, $r, %args); 403 | $r->loggrep("lost child") 404 | and die "httpd lost child"; 405 | } 406 | 407 | sub check_len { 408 | my ($c, $r, %args) = @_; 409 | 410 | $args{len} ||= 512 unless $args{lengths}; 411 | 412 | my @clen; 413 | @clen = $c->loggrep(qr/^LEN: /) or die "no client len" 414 | unless $args{client}{nocheck}; 415 | # !@clen 416 | # or die "client: @clen", "len mismatch"; 417 | !defined($args{len}) || !$clen[0] || $clen[0] eq "LEN: $args{len}\n" 418 | or die "client: $clen[0]", "len $args{len} expected"; 419 | my @lengths = map { ref eq 'ARRAY' ? @$_ : $_ } 420 | @{$args{lengths} || []}; 421 | foreach my $len (@lengths) { 422 | unless ($args{client}{nocheck}) { 423 | my $clen = shift @clen; 424 | $clen eq "LEN: $len\n" 425 | or die "client: $clen", "len $len expected"; 426 | } 427 | } 428 | } 429 | 430 | sub check_md5 { 431 | my ($c, $r, %args) = @_; 432 | 433 | my @cmd5; 434 | @cmd5 = $c->loggrep(qr/^MD5: /) unless $args{client}{nocheck}; 435 | my @md5 = ref($args{md5}) eq 'ARRAY' ? @{$args{md5}} : $args{md5} || () 436 | or return; 437 | foreach my $md5 (@md5) { 438 | unless ($args{client}{nocheck}) { 439 | my $cmd5 = shift @cmd5 440 | or die "too few md5 in client log"; 441 | $cmd5 =~ /^MD5: ($md5)$/ 442 | or die "client: $cmd5", "md5 $md5 expected"; 443 | } 444 | } 445 | @cmd5 && ref($args{md5}) eq 'ARRAY' 446 | and die "too many md5 in client log"; 447 | } 448 | 449 | sub check_loggrep { 450 | my ($c, $r, %args) = @_; 451 | 452 | my %name2proc = (client => $c, httpd => $r); 453 | foreach my $name (qw(client httpd)) { 454 | my $p = $name2proc{$name} or next; 455 | my $pattern = $args{$name}{loggrep} or next; 456 | $pattern = [ $pattern ] unless ref($pattern) eq 'ARRAY'; 457 | foreach my $pat (@$pattern) { 458 | if (ref($pat) eq 'HASH') { 459 | while (my($re, $num) = each %$pat) { 460 | my @matches = $p->loggrep($re); 461 | @matches == $num 462 | or die "$name matches '@matches': ", 463 | "'$re' => $num"; 464 | } 465 | } else { 466 | $p->loggrep($pat) 467 | or die "$name log missing pattern: '$pat'"; 468 | } 469 | } 470 | } 471 | } 472 | 473 | 1; 474 | -------------------------------------------------------------------------------- /regress/tests/httpd.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # $OpenBSD: httpd.pl,v 1.2 2016/05/03 19:13:04 bluhm Exp $ 3 | 4 | # Copyright (c) 2010-2015 Alexander Bluhm 5 | # Copyright (c) 2015 Reyk Floeter 6 | # 7 | # Permission to use, copy, modify, and distribute this software for any 8 | # purpose with or without fee is hereby granted, provided that the above 9 | # copyright notice and this permission notice appear in all copies. 10 | # 11 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 | 19 | use strict; 20 | use warnings; 21 | use Socket; 22 | use Socket6; 23 | 24 | use Client; 25 | use Httpd; 26 | require 'funcs.pl'; 27 | 28 | sub usage { 29 | die "usage: httpd.pl chroot [test-args.pl]\n"; 30 | } 31 | 32 | my $testfile; 33 | our %args; 34 | if (@ARGV and -f $ARGV[-1]) { 35 | $testfile = pop; 36 | do $testfile 37 | or die "Do test file $testfile failed: ", $@ || $!; 38 | } 39 | @ARGV == 1 or usage(); 40 | 41 | my $redo = $args{lengths} && @{$args{lengths}}; 42 | $redo = 0 if $args{client}{http_vers}; # run only one persistent connection 43 | my($sport, $rport) = find_ports(num => 2); 44 | my($d, $c); 45 | $d = Httpd->new( 46 | chroot => $ARGV[0], 47 | listendomain => AF_INET, 48 | listenaddr => "127.0.0.1", 49 | listenport => $rport, 50 | connectdomain => AF_INET, 51 | connectaddr => "127.0.0.1", 52 | connectport => $sport, 53 | %{$args{httpd}}, 54 | testfile => $testfile, 55 | ); 56 | $c = Client->new( 57 | chroot => $ARGV[0], 58 | func => \&http_client, 59 | connectdomain => AF_INET, 60 | connectaddr => "127.0.0.1", 61 | connectport => $rport, 62 | %{$args{client}}, 63 | testfile => $testfile, 64 | ) unless $args{client}{noclient}; 65 | 66 | $d->run; 67 | $d->up; 68 | $c->run->up unless $args{client}{noclient}; 69 | 70 | $c->down unless $args{client}{noclient}; 71 | $d->kill_child; 72 | $d->down; 73 | 74 | check_logs($c, $d, %args); 75 | --------------------------------------------------------------------------------